$npx -y skills add gaia-react/gaia --skill tddTest-driven development with red-green-refactor loop. Use when user wants to build features or fix bugs using TDD, mentions "red-green-refactor", wants integration tests, or asks for test-first development.
| 1 | # Test-Driven Development |
| 2 | |
| 3 | ## Selecting a Stack Reference |
| 4 | |
| 5 | Before writing the first red test, consult the reference for your stack: |
| 6 | |
| 7 | - **React / Vitest / MSW / Storybook** → [references/tests-react.md](references/tests-react.md) |
| 8 | |
| 9 | Add a new `references/tests-{stack}.md` when adopting a new stack. The stack reference covers concrete patterns, test layers, mocking rules, and good/bad examples specific to that environment. |
| 10 | |
| 11 | ## Philosophy |
| 12 | |
| 13 | **Core principle**: tests verify behavior through public interfaces, not implementation details. Code can change entirely; tests shouldn't. |
| 14 | |
| 15 | **Good tests** are integration-style, they exercise real code paths through public APIs and describe _what_ the system does, not _how_. A good test reads like a specification: "user submits a valid form and sees a success toast" tells you exactly what capability exists. These tests survive refactors because they don't care about internal structure. |
| 16 | |
| 17 | **Bad tests** are coupled to implementation. They mock internal collaborators, spy on state setters, or assert on internal call signatures. The warning sign: your test breaks when you refactor, but behavior hasn't changed. |
| 18 | |
| 19 | ## Anti-Pattern: Horizontal Slices |
| 20 | |
| 21 | **DO NOT write all tests first, then all implementation.** This is "horizontal slicing", treating RED as "write all tests" and GREEN as "write all code." |
| 22 | |
| 23 | Tests written in bulk test _imagined_ behavior, not _actual_ behavior. You outrun your headlights, committing to structure before understanding the implementation, producing tests insensitive to real changes. |
| 24 | |
| 25 | **Correct approach**: vertical slices via tracer bullets. One test → one implementation → repeat. |
| 26 | |
| 27 | ``` |
| 28 | WRONG (horizontal): |
| 29 | RED: test1, test2, test3, test4, test5 |
| 30 | GREEN: impl1, impl2, impl3, impl4, impl5 |
| 31 | |
| 32 | RIGHT (vertical): |
| 33 | RED→GREEN: test1→impl1 |
| 34 | RED→GREEN: test2→impl2 |
| 35 | RED→GREEN: test3→impl3 |
| 36 | ... |
| 37 | ``` |
| 38 | |
| 39 | ## Workflow |
| 40 | |
| 41 | ### 1. Planning |
| 42 | |
| 43 | Before writing any code: |
| 44 | |
| 45 | - [ ] Confirm which layer owns this test (see stack reference for layer breakdown) |
| 46 | - [ ] Confirm which behaviors to test (prioritize) |
| 47 | - [ ] Identify opportunities for [deep modules](deep-modules.md), small interface, deep implementation |
| 48 | - [ ] Design interfaces for [testability](interface-design.md) |
| 49 | - [ ] List the behaviors to test (not implementation steps) |
| 50 | - [ ] Get user approval on the plan |
| 51 | |
| 52 | Ask: "What should the public interface look like? Which behaviors are most important to test?" |
| 53 | |
| 54 | **You can't test everything.** Focus on critical paths and complex logic, not every edge case. |
| 55 | |
| 56 | ### 2. Tracer Bullet |
| 57 | |
| 58 | Write ONE test that confirms ONE thing end-to-end for this layer. `RED → GREEN`. The tracer bullet confirms the testing infrastructure wires up before adding real coverage. |
| 59 | |
| 60 | ### 3. Incremental Loop |
| 61 | |
| 62 | For each remaining behavior: `RED → GREEN`. One test at a time. Only enough code to pass the current test. Don't anticipate future tests. |
| 63 | |
| 64 | **Bound the green chase.** If a test won't pass after a few focused attempts, stop and reassess instead of thrashing the implementation: the test, the interface, or an assumption may be wrong. Surface the blocker rather than looping indefinitely to force green. |
| 65 | |
| 66 | #### Authoring an honest RED on the deterministic surface |
| 67 | |
| 68 | The deterministic surface (pure utils, service parsers, spec-derivable hooks) is RED-gated: a new test there commits only after a genuine failing-first run is observed at its current body. |
| 69 | |
| 70 | **Author the test against the not-yet-written or stub implementation symbol.** Write the test for the behavior you are about to build, pointing at a symbol that does not exist yet (or exists only as a stub that returns the wrong value). Run it; it fails because the implementation is missing or incomplete. That failure is the honest RED: a real missing-implementation failure, not a manufactured one. Then write the implementation that turns it green. |
| 71 | |
| 72 | ``` |
| 73 | RIGHT: test names parseAmount() → run → fails (parseAmount undefined / stub) → implement → green |
| 74 | WRONG: implement parseAmount() → write the test → break parseAmount() to force red → restore it → green |
| 75 | ``` |
| 76 | |
| 77 | **Never break working production code to force a red, then restore it.** That pattern relocates the theater into the implementation file: it is mechanically identical to the green-only theater the RED gate condemns, and mutating the body after the RED is captured changes the content signal and invalidates the captured RED, so the gate denies the commit anyway. The honest path is always to author the test against the absent or stub symbol, so the red comes for free from the missing implementation. |
| 78 | |
| 79 | **Single-pass-author exemption.** When the implementation already exists in the same change with no prior failing observation (a single-pass author landing impl and test together), do NOT manufacture a re |