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:
- IF: the test breaks when you rename an internal function but behavior is unchanged → THEN the test is coupled to implementation; rewrite it against the public interface (see
./references/tests.md for good/bad examples)
- IF: the behavior crosses a system boundary (external API, database, time, randomness, filesystem) → THEN mock at that boundary only — never mock internal collaborators (see
./references/mocking.md)
- Example: Next behavior is "checkout with empty cart returns error" → write that test alone, watch it fail, add the minimal guard clause, watch it pass, then run the checklist.
Refactor only while GREEN
- Never refactor while RED. Get to GREEN first.
- After all tests for the current slice pass, look for refactor candidates: duplication, long methods, shallow modules, feature envy, primitive obsession, and existing code the new code reveals as problematic (see
./references/refactoring.md)
- Apply changes:
- Extract duplication
- Deepen modules (move complexity behind simple interfaces)
- Apply SOLID principles where natural
- Consider what the new code reveals about existing code
- Run tests after EACH refactor step. If anything goes red, undo or fix immediately before the next step.
- Example: After three checkout tests are green you notice cart-total math repeated in two places → extract
calculateTotal(cart), run tests, all green → continue.
Return to the loop or finish
- IF: more prioritized behaviors remain → return to step 3
- IF: all prioritized behaviors are covered and refactor pass is clean → summarize what was built, which behaviors are covered, and any deferred behaviors the user explicitly chose not to test
- Example: "Checkout flow done. Covered: valid cart, empty cart, payment failure. Deferred per your call: partial refund (edge case, low priority)."
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.
1---2name: tdd3description: 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`.4---56# Purpose78Drive 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.910## Philosophy1112**Tests verify behavior through public interfaces, not implementation details.** Code can change entirely; tests shouldn't.1314- **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.15- **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.1617**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:1819- Tests written in bulk test _imagined_ behavior, not _actual_ behavior20- You end up testing the _shape_ of things (data structures, function signatures) rather than user-facing behavior21- Tests become insensitive to real changes — they pass when behavior breaks, fail when behavior is fine22- You outrun your headlights, committing to test structure before understanding the implementation2324**Correct approach: vertical slices via tracer bullets.** One test → one implementation → repeat. Each test responds to what you learned from the previous cycle.2526```27WRONG (horizontal):28 RED: test1, test2, test3, test4, test529 GREEN: impl1, impl2, impl3, impl4, impl53031RIGHT (vertical):32 RED→GREEN: test1→impl133 RED→GREEN: test2→impl234 RED→GREEN: test3→impl335 ...36```3738## Workflow39401. **Plan with the user before writing any code**41 - 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.42 - Confirm with the user what interface changes are needed43 - 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 case44 - Identify opportunities for **deep modules** (small interface, deep implementation) — see `./references/deep-modules.md`45 - Design interfaces for **testability** (accept dependencies, return results, small surface) — see `./references/interface-design.md`46 - List behaviors to test (not implementation steps)47 - Get user approval on the plan before proceeding48 - 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.49502. **Fire the tracer bullet**51 - Write ONE test that confirms ONE thing about the system52 - RED: test fails (confirm it fails for the right reason — not a syntax error)53 - GREEN: write the minimum code to make it pass54 - This proves the path works end-to-end before you build more on top of it55 - Example:56 ```57 RED: test("checkout with valid cart returns confirmed status") → fails (no checkout fn)58 GREEN: implement minimal checkout() returning { status: "confirmed" } → test passes59 ```60613. **Loop one behavior at a time**62 - For each remaining behavior, run RED → GREEN63 - Rules:64 - One test at a time65 - Only enough code to pass the current test66 - Do NOT anticipate future tests67 - Keep tests focused on observable behavior through the public interface68 - After each GREEN, run the **per-cycle checklist**:69 - [ ] Test describes behavior, not implementation70 - [ ] Test uses public interface only71 - [ ] Test would survive an internal refactor72 - [ ] Code is minimal for this test73 - [ ] No speculative features added74 - IF: the test breaks when you rename an internal function but behavior is unchanged → THEN the test is coupled to implementation; rewrite it against the public interface (see `./references/tests.md` for good/bad examples)75 - IF: the behavior crosses a system boundary (external API, database, time, randomness, filesystem) → THEN mock at that boundary only — never mock internal collaborators (see `./references/mocking.md`)76 - Example: Next behavior is "checkout with empty cart returns error" → write that test alone, watch it fail, add the minimal guard clause, watch it pass, then run the checklist.77784. **Refactor only while GREEN**79 - Never refactor while RED. Get to GREEN first.80 - After all tests for the current slice pass, look for refactor candidates: duplication, long methods, shallow modules, feature envy, primitive obsession, and existing code the new code reveals as problematic (see `./references/refactoring.md`)81 - Apply changes:82 - Extract duplication83 - Deepen modules (move complexity behind simple interfaces)84 - Apply SOLID principles where natural85 - Consider what the new code reveals about existing code86 - Run tests after EACH refactor step. If anything goes red, undo or fix immediately before the next step.87 - Example: After three checkout tests are green you notice cart-total math repeated in two places → extract `calculateTotal(cart)`, run tests, all green → continue.88895. **Return to the loop or finish**90 - IF: more prioritized behaviors remain → return to step 391 - IF: all prioritized behaviors are covered and refactor pass is clean → summarize what was built, which behaviors are covered, and any deferred behaviors the user explicitly chose not to test92 - Example: "Checkout flow done. Covered: valid cart, empty cart, payment failure. Deferred per your call: partial refund (edge case, low priority)."9394## References9596### Good vs. bad tests, with examples9798- IF: deciding whether a test is well-formed, debugging a test that breaks on refactor, or unsure what to assert99- THEN: Read `./references/tests.md`100- EXAMPLES:101 - "is this test testing implementation or behavior?"102 - "my tests keep breaking when I rename things"103 - "what should I assert here?"104105### Mocking and system boundaries106107- IF: about to mock something, designing an external integration, or wondering if a dependency should be injected108- THEN: Read `./references/mocking.md`109- EXAMPLES:110 - "should I mock the database?"111 - "how do I test this Stripe integration?"112 - "this function is hard to mock — what do I change?"113114### Interface design for testability115116- IF: designing a new function or module, or an existing one is painful to test117- THEN: Read `./references/interface-design.md`118- EXAMPLES:119 - "this function takes too many params to test"120 - "how should I shape this API?"121 - "the test setup is enormous — what's wrong with the interface?"122123### Deep modules (small interface, deep implementation)124125- IF: an interface feels too wide, too many methods, or wraps trivial logic126- THEN: Read `./references/deep-modules.md`127- EXAMPLES:128 - "is this module too shallow?"129 - "should I split or combine these classes?"130 - "this wrapper does almost nothing — keep it?"131132### Refactor candidates after GREEN133134- IF: all tests pass and you're entering the refactor step135- THEN: Read `./references/refactoring.md`136- EXAMPLES:137 - "what should I refactor now that tests pass?"138 - "I see duplication — extract or leave?"139 - "the new code makes the old code look bad — what do I do?"140141## Works well with142143Optional collaborators — `tdd` runs standalone and these degrade gracefully if absent.144145- **`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.146- **`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.147- **`diagnose`** — after a diagnosis pins a bug, lock the fix with a `tdd` regression test.148- **`setup-toolbox-context`** — provides the `CONTEXT.md` vocabulary `tdd` uses for test and interface names.