Test-Driven Development (TDD)
The Iron Law
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
Write code before the test? Delete it. Start over.
No exceptions:
- •Don't keep it as "reference"
- •Don't "adapt" it while writing tests
- •Don't look at it
- •Delete means delete
Violating the letter of the rules is violating the spirit of the rules.
When to Use
Always: New features, bug fixes, refactoring, behavior changes.
Exceptions (ask your human partner): Throwaway prototypes, generated code, config files.
Red-Green-Refactor
RED — Write Failing Test
Write one minimal test showing what should happen.
Requirements:
- •One behavior per test
- •Clear descriptive name
- •Real code (no mocks unless unavoidable)
Verify RED — Watch It Fail
MANDATORY. Never skip.
# Run the specific test <test command> path/to/test
Confirm:
- •Test fails (not errors from typos)
- •Failure message is expected
- •Fails because feature is missing
Test passes? You're testing existing behavior. Fix the test.
GREEN — Minimal Code
Write simplest code to pass the test.
Don't add features, refactor other code, or "improve" beyond the test.
Verify GREEN — Watch It Pass
MANDATORY.
Confirm:
- •Test passes
- •Other tests still pass
- •Output clean (no errors, warnings)
Test fails? Fix code, not test.
REFACTOR — Clean Up
After green only:
- •Remove duplication
- •Improve names
- •Extract helpers
Keep tests green. Don't add behavior.
Repeat
Next failing test for next behavior.
Good vs Bad Tests
| Quality | Good | Bad |
|---|---|---|
| Minimal | One thing. "and" in name? Split it. | Tests multiple behaviors |
| Clear | Name describes behavior | test('test1') |
| Real | Tests actual code | Tests mock behavior |
Common Rationalizations
| Excuse | Reality |
|---|---|
| "Too simple to test" | Simple code breaks. Test takes 30 seconds. |
| "I'll test after" | Tests passing immediately prove nothing. |
| "Need to explore first" | Fine. Throw away exploration. Start with TDD. |
| "Test hard = skip it" | Hard to test = hard to use. Fix the design. |
| "TDD will slow me down" | TDD is faster than debugging. |
| "Already manually tested" | Ad-hoc ≠ systematic. No record, can't re-run. |
| "Deleting X hours is wasteful" | Sunk cost fallacy. Keeping unverified code is debt. |
Bug Fix Flow
- •RED: Write test reproducing the bug
- •Verify RED: Watch it fail with the bug
- •GREEN: Fix the bug (minimal code)
- •Verify GREEN: Test passes, all tests pass
- •Commit
Never fix bugs without a test.
Verification Checklist
Before marking work complete:
- • Every new function has a test
- • Watched each test fail before implementing
- • Each test failed for expected reason
- • Wrote minimal code to pass each test
- • All tests pass
- • Output clean
- • Tests use real code (mocks only if unavoidable)
- • Edge cases and errors covered
Can't check all boxes? You skipped TDD. Start over.