Test-Driven Development (TDD)
Write the test first. Watch it fail. Write minimal code to pass.
Core principle: If you didn't watch the test fail, you don't know if it tests the right thing.
The Iron Law
code
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
Write code before the test? Delete it. Start over.
Red-Green-Refactor
RED — Write Failing Test
Write one minimal test showing what should happen.
typescript
test('rejects empty email', async () => {
const result = await submitForm({ email: '' });
expect(result.error).toBe('Email required');
});
Requirements:
- •One behavior
- •Clear name
- •Real code (no mocks unless unavoidable)
Verify RED — Watch It Fail
MANDATORY. Never skip.
bash
npm test path/to/test.test.ts
Confirm:
- •Test fails (not errors)
- •Failure message is expected
- •Fails because feature missing
GREEN — Minimal Code
Write simplest code to pass the test.
typescript
function submitForm(data: FormData) {
if (!data.email?.trim()) {
return { error: 'Email required' };
}
// ...
}
Don't add features, refactor other code, or "improve" beyond the test.
Verify GREEN — Watch It Pass
MANDATORY.
bash
npm test path/to/test.test.ts
Confirm:
- •Test passes
- •Other tests still pass
- •Output pristine
REFACTOR — Clean Up
After green only:
- •Remove duplication
- •Improve names
- •Extract helpers
Keep tests green. Don't add 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. |
| "Already manually tested" | Ad-hoc ≠ systematic. No record, can't re-run. |
| "TDD will slow me down" | TDD faster than debugging. |
Red Flags — STOP and Start Over
- •Code before test
- •Test passes immediately
- •Can't explain why test failed
- •"Just this once"
Verification Checklist
Before marking work complete:
- • Every new function has a test
- • Watched each test fail before implementing
- • Wrote minimal code to pass each test
- • All tests pass
- • Output pristine (no errors, warnings)