| name | test-driven-development |
| description | (TDD) Use when implementing any feature or bugfix. Apply the Logic Gate to identify what needs tests, then use the Iron Rule for strict test-first development on logic. |
Test-Driven Development (TDD)
The Two Gates
Before writing any code, pass it through two gates:
- The Logic Gate — Triage. Does this piece contain logic?
- The Iron Rule — For everything that passes the Logic Gate, strict TDD applies.
flowchart TD
A[New code to write] --> B[Decompose into pieces]
B --> C{{"Logic Gate: Does this piece contain logic?"}}
C -->|"Conditionals, transformations, validation, state changes, error handling"| D["Iron Rule: TDD"]
C -->|"Dataclasses, config, thin wrappers, type aliases, formatting"| E[Write directly]
D --> F["RED: Write failing test"]
F --> G["GREEN: Minimal implementation"]
G --> H["REFACTOR: Clean up"]
H --> I{More pieces?}
E --> I
I -->|Yes| C
I -->|No| J[Done]
Gate 1: The Logic Gate
Decompose the work into its constituent pieces. For each piece, ask: does this contain logic?
Passes the Logic Gate (requires TDD):
- Conditionals and branching
- Data transformations and computations
- Validation and parsing
- State management and transitions
- Error handling with recovery logic
- Business rules and domain logic
- Algorithm implementations
Does NOT pass the Logic Gate (write directly):
- Dataclasses, NamedTuples, TypedDicts (pure data declarations)
- Type aliases and protocol definitions
- Configuration and constants
- Thin wrappers that only delegate (no branching, no transformation)
- Plot formatting and visual styling
- Boilerplate (
__init__.py, module-level setup)
- String templates and static content
When in doubt: If you can't point to a conditional, transformation, or state change, it probably doesn't pass the Logic Gate.
Triage Output
Before writing code, briefly identify what passes and what doesn't:
- "These pieces contain logic and need TDD: X, Y"
- "These are declarations/glue and can be written directly: Z"
Gate 2: The Iron Rule
For code that passes the Logic Gate: never write logic without a preceding failing test.
If logic is written before its test:
- Stop — the test you write now can only describe what you already wrote, not verify intended behavior
- Delete the logic and restart test-first
- This applies to logic specifically, not declarations
Red-Green-Refactor Cycle
RED: Write a Failing Test
Write a single minimal test demonstrating intended behavior:
- Clear, descriptive name
- Test actual functionality (not mocks unless necessary)
- Focus on one behavior
- If test name contains "and", split into separate tests
Verify RED: Confirm Failure
Run tests and confirm:
- Test fails for the expected reason (missing feature)
- Not failing due to syntax errors
- This verification is non-negotiable
GREEN: Minimal Implementation
Write the simplest possible code satisfying the test:
- Avoid feature creep
- Don't refactor unrelated code
- Don't over-engineer
Verify GREEN: Confirm Pass
Confirm:
- The new test passes
- No existing tests break
REFACTOR: Clean Up
Improve code quality while maintaining green status:
- Eliminate duplication
- Improve naming
- Simplify logic
- Run tests after each change
Quality Test Attributes
Minimal: Single responsibility per test
Clear: Descriptive name revealing behavior
Realistic: Demonstrate intended API usage
Focused: One assertion per test (generally)
Common Rationalizations to Reject
"I'll test afterward"
- Tests written post-implementation pass immediately
- Proves nothing about validation capability
"Manual testing covered edge cases"
- Lacks systematic documentation
- Not reproducible
"Tests-after achieve identical goals"
- Tests-first answer: "What should happen?"
- Tests-after answer: "What does this do?"
- Fundamentally different approaches
Verification Checklist
Before considering work complete:
Debugging Integration
Bug found? Write failing test reproducing it. Follow TDD cycle. Test proves fix and prevents regression.
Never fix bugs without a test.