--- name: tdd description: "Test-driven development in any language. Use it when the user wants to build a feature or fix a bug test-first, mentions \"TDD\", \"red-green-refactor\" or \"test first\", or asks for integration tests." license: MIT --- # Test-Driven Development Respond in the user's language; keep common technical terms in English. TDD is the red → green cycle, and it is above all a **design tool** for the dev: the test written first forces you to decide the interface from the point of view of whoever uses it. It is not a certification mechanism: the dev writing their own tests does not prove that the feature meets the spec (see "Double loop" below). This skill is the reference that makes that cycle produce tests worth keeping: what a good test is, where tests go, the anti-patterns and the rules of the cycle. Each section applies on every cycle: consult them before and during, not after. Analogy: the red test is the blueprint of the hole you are going to fill; if you write the code first, you are filling without knowing the shape of the hole. When exploring the code, read `GLOSSARY.md` (if it exists) so that the names of the tests and interfaces match the language of the domain, and respect the area's ADRs. ## Language This skill is language-agnostic. Identify the language of the code under test by its file extensions (`.go` → `go`, `.php` → `php`, `.ts`/`.tsx` → `typescript`, `.py` → `python`). If `-testing` exists among your available skills, load it (in Claude Code, with the Skill tool; in other tools, by mentioning its name or reading its SKILL.md) before the first cycle: it brings that language's test idioms, examples, preferred test doubles and commands, and its rules take precedence over the generic examples here. If it does not exist, carry on with this skill alone and say so in one line. ## What a good test is Tests verify behavior through public interfaces, not implementation details. The code can change completely; the tests should not. A good test reads like a specification: "the customer can pay with a valid cart" says exactly which capability exists, and it survives refactors because it does not care about the internal structure. Examples in [tests.md](tests.md) and a guide to test doubles in [mocking.md](mocking.md). ## Seams: where tests go A **seam** is the public boundary where you test: the interface at which you observe behavior without reaching inside. Tests live at seams, never against internals. Typical seams: a module's or package's public API, an HTTP endpoint, a queue consumer, a CLI. **Test only at agreed seams.** Before writing any test, write down the seams and confirm them with the user. No test is written at an unconfirmed seam. You cannot test everything; agreeing the seams beforehand is how the testing effort lands on the critical paths and the complex logic instead of on every edge case. Ask: "What is the public interface and which seams do we test?". Give each proposed seam a one-line note on what it catches and what it misses. When the shape of that interface is in doubt (how deep the module is, where the seam goes, what it should expose), invoke the `codebase-design` skill for the vocabulary. It is the shared source of the terms module, interface, depth, seam, adapter, leverage and locality; it is a reference you consult, not a session you run. ## Anti-patterns - **Coupled to the implementation**: mocks internal collaborators, tests private functions, or verifies through a side channel (querying the database instead of using the interface). The sign: the test breaks on a refactor even though the behavior did not change. - **Tautological**: the assertion recomputes the expected value the same way the code does (the expected value of `add(a, b)` computed as `a + b`, a snapshot generated by the code itself and never reviewed, a constant compared with itself), so it passes by construction and can never disagree. Expected values come from an independent source of truth: a known literal, an example worked out by hand, the spec. - **Horizontal slicing** (in the inner loop): writing all your tests first and then all the implementation. It does not apply to QA's acceptance tests, which are specified upfront on purpose: they are the outer loop, not your design tests. Tests written in bulk verify _imagined_ behavior: they test the _shape_ of things instead of the behavior, become insensitive to real changes and lock in the test structure before the implementation is understood. Work in **vertical slices**: one test → one implementation → repeat; each test is a **tracer bullet** that responds to what the previous cycle taught. ## Rules of the cycle - **Red before green.** First write the failing test and watch it fail by running only that test; then write only the code needed to make it pass. Do not anticipate future tests or add speculative functionality. - **One slice at a time.** One seam, one test, one minimal implementation per cycle. - **Refactoring is not part of the cycle.** It belongs to the review stage (see the `code-review` skill), not to the red → green cycle. ## Double loop: the acceptance tests TDD is the **inner loop**. If the repo uses the `qa` plugin (`docs/agents/qa.md` exists), there is an **outer loop**: the acceptance tests the QA role wrote from the spec (double-loop TDD, Freeman and Pryce). Analogy: the inner loop is the carpenter checking each joint with their square; the outer loop is the customer checking whether the drawer opens and closes. The carpenter cannot redefine what "the drawer works" means. Rules: - **The acceptance tests are read-only for you.** You do not edit them, skip them, add exclusion tags or change their data, even if you are sure they are wrong. They are sealed: any change breaks the seal and the verification detects it. - **If an acceptance test looks wrong, escalate**: record an entry in `.scratch//qa/escalations.md` with the scenario, your argument and the evidence, and move on to another scenario. QA or the human decides. - **No special-casing**: production code never recognizes the tests' data, IDs (`SC-NN`), paths or variables to answer what they expect. It is the other way of cheating, and `qa-verify` looks for it in the diff. - **Do not run them on every cycle**: during implementation almost all of them are red by design. Work with your inner-loop tests and run the ticket's acceptance tests **when closing the slice**, with `qa-verify` in per-slice cadence if available. - **A ticket is not done** while its acceptance scenarios are not green with the seal intact. Your green tests are not enough. - If `qa-verify` hands you a code-class **finding** (F-NN), its reproduction command is the starting point: push it down into an integration or unit test that reproduces it red, and that test stays in your suite as a regression test.