| name | test-driven-development |
| description | Use when implementing features/fixes. Iron law: ¬∃ production code without failing test. |
Test-Driven Development
When to Use
∀ features/fixes where TDD applies. Simple tasks may skip TDD.
HARD-GATE
¬∃ production_code without failing_test_first. If code ∃ before test -> DELETE IT ∧ start over. Sunk cost irrelevant — time gone regardless.
Cycle
RED(write 1 test -> run -> observe FAIL) -> GREEN(minimal code to pass) -> REFACTOR(structure, tests stay green)
RED
it('should validate email format', () => {
const result = validateEmail('not-an-email');
expect(isErr(result)).toBe(true);
});
Rules: 1 behavior/test | descriptive name | describe/it/expect (¬test()) | MUST run ∧ watch FAIL
∀ test: fails for right reason (missing feature ¬syntax error). GREEN before impl -> feature ∃ ∨ test wrong — investigate.
GREEN
export const validateEmail = (email: string): Result<string, Error> => {
if (!email.includes('@')) return Err(new Error('Invalid email'));
return Ok(email);
};
Only enough code to pass. No more.
REFACTOR
Improve structure, ¬change behavior, tests stay green.
Agent Routing (standard/complex)
- Tester subagent writes failing .spec.ts -> commits
- Domain subagent implements -> tests pass -> commits
- Tester subagent verifies coverage ∧ edge cases
Sunk Cost Rule
Deleting hours of work to start test-first is correct. Time gone regardless. Starting over w/ proper TDD produces better code faster than retrofitting tests.
3-Fix Red Flag
3+ test fixes attempted -> question the design, ¬ the test. Test probably right; impl approach wrong.
Anti-Patterns
| Pattern | Problem | Fix |
|---|
| Tests after impl | Never verified test catches failures | Write test first -> watch fail -> implement |
| Mock behavior testing | expect(mock).toHaveBeenCalled() tests mock, ¬code | Use ∈-memory adapters implementing real interface |
| Over-mocking | Mock everything, test nothing real | ∈-memory adapters: real interface, ¬I/O |
| "Too simple for TDD" | Simple code is where hidden bugs live | Follow the cycle regardless |
Gate (∀ must be true before DONE)
- ∀ tests failed before impl? 2. Suite passes? 3. Testing behavior ∨ mock? (mock -> stop) 4. Catches regression? (¬ -> rewrite)
Framework
Vitest: describe/it/expect/beforeEach/afterEach | ¬test() | colocated .spec.ts | globals: true