| name | test-driven-development |
| description | Enforce strict test-driven development with the Iron Law: no production code without a failing test first. Use when implementing any feature, fixing any bug, or modifying any behavior. Covers Red-Green-Refactor cycle, test pyramid, Prove-It Pattern for bugs, and DAMP test writing.
|
| version | 1.0.0 |
| permissions | ["fs.read","fs.write","shell.exec"] |
| triggers | [{"context":"implementing a feature"},{"context":"fixing a bug"},{"context":"writing code"},{"context":"modifying behavior"},{"command":"/build"},{"command":"/test"}] |
| platforms | ["claude-code","cursor","gemini-cli","copilot","codex","opencode","windsurf","kiro"] |
| dependencies | ["verification-before-completion@^1.0.0"] |
| model_variants | {"claude":{"enforcement_style":"human-partner collaborative","iron_law_phrasing":"Your human partner expects tests first. Write the failing test, show it fails, then implement."},"gemini":{"enforcement_style":"structured-checklist","iron_law_phrasing":"CHECKLIST: ☐ Write failing test ☐ Confirm RED ☐ Write minimal code ☐ Confirm GREEN ☐ Refactor"},"gpt":{"enforcement_style":"directive-imperative","iron_law_phrasing":"You MUST write a failing test before any production code. No exceptions. Violation = delete the code."}} |
| author | codehands-core |
| signed | true |
| tier | 0 |
Overview
Test-Driven Development is the single most impactful engineering practice for AI coding agents. Without it, agents produce untested, unreliable code. CodeHands enforces the strictest TDD discipline: the Iron Law.
When to Use
- When implementing ANY new feature or behavior
- When fixing ANY bug (use the Prove-It Pattern)
- When refactoring existing code (tests must pass before and after)
- When modifying API contracts (update contract tests first)
- NOT when writing documentation, configuration, or non-code files
The Iron Law
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST.
If you wrote production code before writing a test, delete it. Not "save it as reference." Not "keep it and add a test." DELETE it. Then write the test. Then rewrite the production code to make the test pass.
This is not a guideline. This is the law.
Why deletion, not "just add a test after"? Because:
- Tests written after the code test the implementation, not the requirement
- You cannot verify the test would have caught a real bug if it was written to match existing code
- It reinforces the discipline that prevents the rationalization spiral
Process
Phase 1: RED — Write a Failing Test
- Understand the requirement. What should the code do? What are the inputs and outputs?
- Write a test that describes the desired behavior. Use DAMP names (Descriptive And Meaningful Phrases):
GOOD: test_returns_404_when_user_not_found()
BAD: test_get_user_error()
- Run the test. Confirm it FAILS. If it passes, your test is wrong — it's not testing new behavior.
- Read the failure message. It should clearly describe what's missing.
Phase 2: GREEN — Make It Pass
- Write the MINIMUM code to make the test pass. Not elegant code. Not complete code. The minimum.
- Run the test. Confirm it PASSES.
- Run the full test suite. Confirm nothing else broke.
Phase 3: REFACTOR — Clean Up
- Now improve the code. Extract functions, rename variables, remove duplication.
- Run the test suite after every change. If tests fail, your refactoring changed behavior — undo it.
- Commit. The test and the code go in the same commit.
Repeat
Each cycle should take 2-10 minutes. If a cycle takes longer than 15 minutes, you're writing too much at once — break it down.
Test Pyramid
Follow the test pyramid for balanced coverage:
╱ E2E ╲ 5% — Full user journeys
╱─────────╲
╱Integration╲ 15% — Component boundaries
╱─────────────╲
╱ Unit Tests ╲ 80% — Individual functions/modules
╱─────────────────╲
| Level | Scope | Speed | # of Tests |
|---|
| Unit | Single function/module | < 10ms | Many (80%) |
| Integration | Component boundaries, API contracts | < 1s | Moderate (15%) |
| E2E | Full user journeys | Seconds | Few (5%) |
The Beyonce Rule: "If you liked it, you should have put a test on it." If a behavior is important enough to keep, it's important enough to test.
The Prove-It Pattern (Bug Fixes)
For every bug fix, follow this exact sequence:
- Reproduce — Write a test that exposes the bug. Run it. It MUST fail.
- Fix — Implement the minimal fix.
- Verify — Run the reproduction test. It MUST pass.
- Regression — Run the full test suite. Nothing else should break.
This proves the bug was real, the fix works, and nothing regressed. Skip any step and you haven't proven anything.
Test Quality Standards
DAMP Over DRY in Tests
Tests should be Descriptive And Meaningful Phrases, even at the cost of some repetition:
def test_login_fails_with_expired_token():
user = create_user(email="test@example.com")
token = create_token(user, expired=True)
response = login(token=token)
assert response.status == 401
assert response.error == "Token expired"
def test_expired_token():
response = self.login(self.expired_token)
self.assert_unauthorized(response)
What Makes a Bad Test
- Tests implementation, not behavior. If you can refactor without changing behavior and the test breaks, it's a bad test.
- Flaky. If it fails intermittently, fix or delete it. Flaky tests erode trust.
- Non-deterministic. Tests that depend on time, random numbers, or external services without mocking.
- Snapshot abuse. Snapshot tests for complex objects create maintenance burden with no behavioral insight.
Common Rationalizations
| Rationalization | Reality |
|---|
| "I'll write the tests after the code works" | This is the #1 cause of untested production code. Once code "works," there's no incentive to test it. Write tests first. |
| "This function is too simple to test" | If it's too simple to test, it's too simple to break. But it CAN break — via integration, refactoring, or dependency changes. Test it. |
| "I need to see if the approach works first" | Use a spike branch. Throw the spike away. Then TDD the real implementation. Spikes that become production code are the enemy. |
| "The test would just be testing the framework" | If your code delegates to a framework, test the delegation — not the framework internals. Verify you're calling the right method with the right arguments. |
| "There's no existing test infrastructure" | Setting up the test framework IS the first task. Create the test runner, write one trivial test, verify it passes. Then proceed. |
| "I already know this code works" | You don't know it works. You believe it works. Belief is not evidence. Write the test. |
| "I'll keep the code as reference and add tests" | No. Delete the code. If you can't rewrite it from the test, you didn't understand it well enough to ship it. |
| "TDD doesn't work for UIs" | TDD works for UI logic, state management, and data transformation. Use component tests. Visual aspects can use snapshot tests sparingly. |
Red Flags
- Production code committed without corresponding tests
- Tests that were clearly written after the code (test names mirror implementation, not behavior)
- Test file added in a DIFFERENT commit than the production code
- "I tested it manually" as justification
- Test suite not run before committing
- High code coverage but tests that don't assert meaningful behavior
- Zero test failures in a development session (suggests tests aren't being written first)
- Spike code that becomes production code without being rewritten via TDD
Verification
See Also
codehands:verification-before-completion — Confirm work is actually done
codehands:systematic-debugging — When tests reveal deeper issues
codehands:code-review-and-quality — Tests reviewed alongside code
references/testing-patterns.md — Framework-specific test patterns
references/testing-anti-patterns.md — Common testing mistakes