| name | tdd |
| description | Test-driven development via the red-green-refactor loop using vertical slices (one test → one implementation at a time). Tests verify behavior through public interfaces, not implementation details. Use when user wants to build a feature or fix a bug test-first, says "TDD", "red-green-refactor", "test-first", "write tests first", asks for integration tests, or invokes `/tdd`. |
Purpose
Drive feature development and bug fixes through tight red-green-refactor cycles, one behavior at a time. Tests verify observable behavior through public interfaces so they survive internal refactors. Vertical slices (tracer bullets) replace the "write all tests, then all code" anti-pattern because tests written in bulk inevitably test imagined behavior — not actual behavior.
Philosophy
Tests verify behavior through public interfaces, not implementation details. Code can change entirely; tests shouldn't.
- Good tests are integration-style: they exercise real code paths through public APIs. They describe what the system does, not how. A good test reads like a specification — "user can checkout with valid cart" tells you exactly what capability exists. These tests survive refactors because they don't care about internal structure.
- Bad tests are coupled to implementation. They mock internal collaborators, test private methods, or verify through external means (querying a database directly instead of using the interface). The warning sign: your test breaks when you refactor, but behavior hasn't changed. If you rename an internal function and tests fail, those tests were testing implementation, not behavior.
Anti-pattern: horizontal slices. DO NOT write all tests first, then all implementation. That treats RED as "write all tests" and GREEN as "write all code." It produces crap tests because:
- Tests written in bulk test imagined behavior, not actual behavior
- You end up testing the shape of things (data structures, function signatures) rather than user-facing behavior
- Tests become insensitive to real changes — they pass when behavior breaks, fail when behavior is fine
- You outrun your headlights, committing to test structure before understanding the implementation
Correct approach: vertical slices via tracer bullets. One test → one implementation → repeat. Each test responds to what you learned from the previous cycle.
WRONG (horizontal):
RED: test1, test2, test3, test4, test5
GREEN: impl1, impl2, impl3, impl4, impl5
RIGHT (vertical):
RED→GREEN: test1→impl1
RED→GREEN: test2→impl2
RED→GREEN: test3→impl3
...
Workflow
-
Plan with the user before writing any code
- Explore the codebase. Use the project's domain glossary so test names and interface vocabulary match the project's language. Respect ADRs in the area you're touching.
- Confirm with the user what interface changes are needed
- Confirm with the user which behaviors to test, and prioritize them — you can't test everything, so focus on critical paths and complex logic, not every possible edge case
- Identify opportunities for deep modules (small interface, deep implementation) — see
./references/deep-modules.md
- Design interfaces for testability (accept dependencies, return results, small surface) — see
./references/interface-design.md
- List behaviors to test (not implementation steps)
- Get user approval on the plan before proceeding
- Example: User asks to add a checkout flow → ask "What should the public
checkout() interface look like? Which behaviors matter most — happy path, invalid cart, payment failure, partial refund?" Get explicit prioritization before any test is written.
-
Fire the tracer bullet
-
Loop one behavior at a time
- For each remaining behavior, run RED → GREEN
- Rules:
- One test at a time
- Only enough code to pass the current test
- Do NOT anticipate future tests
- Keep tests focused on observable behavior through the public interface
- After each GREEN, run the per-cycle checklist:
References
Good vs. bad tests, with examples
- IF: deciding whether a test is well-formed, debugging a test that breaks on refactor, or unsure what to assert
- THEN: Read
./references/tests.md
- EXAMPLES:
- "is this test testing implementation or behavior?"
- "my tests keep breaking when I rename things"
- "what should I assert here?"
Mocking and system boundaries
- IF: about to mock something, designing an external integration, or wondering if a dependency should be injected
- THEN: Read
./references/mocking.md
- EXAMPLES:
- "should I mock the database?"
- "how do I test this Stripe integration?"
- "this function is hard to mock — what do I change?"
Interface design for testability
- IF: designing a new function or module, or an existing one is painful to test
- THEN: Read
./references/interface-design.md
- EXAMPLES:
- "this function takes too many params to test"
- "how should I shape this API?"
- "the test setup is enormous — what's wrong with the interface?"
Deep modules (small interface, deep implementation)
- IF: an interface feels too wide, too many methods, or wraps trivial logic
- THEN: Read
./references/deep-modules.md
- EXAMPLES:
- "is this module too shallow?"
- "should I split or combine these classes?"
- "this wrapper does almost nothing — keep it?"
Refactor candidates after GREEN
- IF: all tests pass and you're entering the refactor step
- THEN: Read
./references/refactoring.md
- EXAMPLES:
- "what should I refactor now that tests pass?"
- "I see duplication — extract or leave?"
- "the new code makes the old code look bad — what do I do?"
Works well with
Optional collaborators — tdd runs standalone and these degrade gracefully if absent.
grill-with-docs (or grill-me) — align on scope, interface, and behaviors before the first test; grill-with-docs also records the ADRs tdd should respect.
living-plan — its build workflow delegates the test-first implementation of each task to tdd, handing over the plan's behaviors as the approved design. tdd stays fully usable without it.
diagnose — after a diagnosis pins a bug, lock the fix with a tdd regression test.
setup-toolbox-context — provides the CONTEXT.md vocabulary tdd uses for test and interface names.