Test-Driven Development
When to use
- Implementing a feature, fixing a bug, or changing core logic.
- Any
tdd-rounds Builder invocation (mandatory every round).
- The user mentions "TDD", "test-first", "red-green-refactor", "integration tests".
When to skip
- Trivial UI glue, framework wiring, config changes, pure docs edits.
- Trivial getters / setters with no behavior.
- Bug whose root cause isn't yet known — run
debug first; the reproduction crystallises into the failing test.
Pre-conditions
- Current branch is not
main / master. If it is, stop and run git checkout -b feat/<short-name> (or fix/...) before writing the first test. Code lands on a feature branch; main receives merges, not commits.
- A feature doc (
docs/features/<short-name>.md) exists with testable ACs — or a debug reproduction names the root cause.
Philosophy
Tests verify behavior through public interfaces, not implementation details. Code can change entirely; tests shouldn't.
- Good tests read like specifications: "user can checkout with valid cart". They use the public API and survive refactors.
- Bad tests are coupled to internals: they mock internal collaborators, assert on call order, or peek at private state. The warning sign — the test breaks during a refactor when behavior didn't change.
See TESTS.md for concrete good/bad examples and a pre-commit checklist.
Anti-pattern: Horizontal Slicing
Do not write all tests first, then all implementation. This is the most common TDD failure mode for AI agents — you write five tests in one shot, then five matching functions. The tests describe imagined behavior, become insensitive to real bugs, and lock in the wrong shape.
WRONG (horizontal):
RED: test1, test2, test3, test4, test5
GREEN: impl1, impl2, impl3, impl4, impl5
RIGHT (vertical):
RED → GREEN: test1 → impl1
RED → GREEN: test2 → impl2
...
One test → minimum code to pass → next test. Each cycle informs the next.
Workflow
1. Red — Write a failing test
- Pick one acceptance criterion from the feature doc.
- Write a single test that asserts the behavior through the public interface.
- Name the test after the behavior, not the function:
returns_empty_list_when_no_users, not test_getUsers.
- Run it. Confirm it fails for the right reason (assertion failed, not import error).
2. Green — Minimum code to pass
- Smallest amount of code that makes the test pass.
- Hardcoding a return value is acceptable on the first test — the next test forces generalization.
- Resist adding features the test does not require.
3. Refactor — Clean up with the test as a safety net
- Remove duplication, improve names, extract functions.
- Run tests after every change.
- Do not add new behavior during refactor.
- Never refactor while red. Get to green first.
4. Simplify pass — end-of-round, after green
Before declaring the round done, run the simplify skill. It walks every changed file with four lenses — reuse, quality, efficiency, test relevance — and is a single sweep, not a license to refactor everything. If you find structural issues that need a dedicated round, surface them as Open questions for parent instead.
Rules
- One behavior per test.
- No production code without a failing test first (for core logic).
- Test through public interfaces only — don't peek at private state or internal calls.
- If a test is hard to write, the design is probably wrong — fix the design, not the test.
- Test-after is acceptable for: UI wiring, framework glue, trivial getters/setters.
Test Pyramid (default targets)
- ~70% unit tests — fast, isolated.
- ~20% integration tests — real dependencies where it matters (DB, HTTP).
- ~10% end-to-end — only the critical user paths.
Anti-patterns
- Writing tests after the code "to get coverage" — defeats the design feedback loop.
- Mocking everything — tests pass but the system is broken. Mock at boundaries only (external APIs, time, randomness).
- One giant test asserting many things — failures become hard to diagnose.
- Asserting on internal call counts or order — couples the test to implementation.
When invoked as a Builder for a multi-round project
If the parent agent dispatched you with a tdd-rounds-shaped brief listing "Skills (in order): design, tdd, simplify": execute all three sequentially in this single invocation. Do not return to the parent between skills. "In order" means run them in that order WITHIN this run — not handed off across separate invocations. Returning early after only design is the most common Builder failure mode and forces the parent to re-dispatch.
The tdd-rounds skill captures the full orchestration pattern (Builder brief schema, structured report shape, parent verification ritual). Read it if your brief references it.
Pairing with other skills
feature-doc — runs before. ACs become the test list.
debug — runs before for non-trivial bugs. Reproduction → failing test.
design — runs alongside. If TDD feels painful, the design is wrong.
simplify — runs after green. End-of-slice / end-of-round sweep.
prod-ready — runs after green (single feature) before opening the PR.
tdd-rounds — wraps tdd for multi-round projects.
Done when
- All ACs from the feature doc are green and the tests are committed.
- Tests assert behavior through public interfaces, not internals.
- The simplify pass has run.
- For single-feature flow:
prod-ready is queued. For tdd-rounds: the structured Builder report is emitted.
Handoff
When all acceptance criteria are green:
- For a single-feature project: run
prod-ready before opening the PR. Catches operational, infrastructure, and consistency issues tests don't surface.
- For a multi-round project orchestrated under
tdd-rounds: emit the structured Builder report (per tdd-rounds/templates/builder-report.md) and stop. The parent runs prod-ready and verify-real-deps at the project level.
1---2name: tdd3description: Test-driven development with the red-green-refactor loop. Use when implementing a feature, fixing a bug, changing core logic, or when the user mentions "TDD", "test-first", "red-green-refactor", or "integration tests". Skip for trivial UI glue, config changes, or pure docs edits.4---56# Test-Driven Development78## When to use910- Implementing a feature, fixing a bug, or changing core logic.11- Any `tdd-rounds` Builder invocation (mandatory every round).12- The user mentions "TDD", "test-first", "red-green-refactor", "integration tests".1314## When to skip1516- Trivial UI glue, framework wiring, config changes, pure docs edits.17- Trivial getters / setters with no behavior.18- Bug whose root cause isn't yet known — run [`debug`](../debug/SKILL.md) first; the reproduction crystallises into the failing test.1920## Pre-conditions2122- **Current branch is not `main` / `master`.** If it is, stop and run `git checkout -b feat/<short-name>` (or `fix/...`) before writing the first test. Code lands on a feature branch; `main` receives merges, not commits.23- A feature doc (`docs/features/<short-name>.md`) exists with testable ACs — or a `debug` reproduction names the root cause.2425## Philosophy2627Tests verify **behavior through public interfaces**, not implementation details. Code can change entirely; tests shouldn't.2829- **Good tests** read like specifications: "user can checkout with valid cart". They use the public API and survive refactors.30- **Bad tests** are coupled to internals: they mock internal collaborators, assert on call order, or peek at private state. The warning sign — the test breaks during a refactor when behavior didn't change.3132See [TESTS.md](TESTS.md) for concrete good/bad examples and a pre-commit checklist.3334## Anti-pattern: Horizontal Slicing3536**Do not write all tests first, then all implementation.** This is the most common TDD failure mode for AI agents — you write five tests in one shot, then five matching functions. The tests describe *imagined* behavior, become insensitive to real bugs, and lock in the wrong shape.3738```39WRONG (horizontal):40 RED: test1, test2, test3, test4, test541 GREEN: impl1, impl2, impl3, impl4, impl54243RIGHT (vertical):44 RED → GREEN: test1 → impl145 RED → GREEN: test2 → impl246 ...47```4849One test → minimum code to pass → next test. Each cycle informs the next.5051## Workflow5253### 1. Red — Write a failing test5455- Pick one acceptance criterion from the feature doc.56- Write a single test that asserts the behavior through the public interface.57- Name the test after the behavior, not the function: `returns_empty_list_when_no_users`, not `test_getUsers`.58- Run it. Confirm it fails for the **right reason** (assertion failed, not import error).5960### 2. Green — Minimum code to pass6162- Smallest amount of code that makes the test pass.63- Hardcoding a return value is acceptable on the first test — the next test forces generalization.64- Resist adding features the test does not require.6566### 3. Refactor — Clean up with the test as a safety net6768- Remove duplication, improve names, extract functions.69- Run tests after every change.70- Do not add new behavior during refactor.71- **Never refactor while red.** Get to green first.7273### 4. Simplify pass — end-of-round, after green7475Before declaring the round done, run the [`simplify`](../simplify/SKILL.md) skill. It walks every changed file with four lenses — reuse, quality, efficiency, test relevance — and is a single sweep, not a license to refactor everything. If you find structural issues that need a dedicated round, surface them as `Open questions for parent` instead.7677## Rules7879- One behavior per test.80- No production code without a failing test first (for core logic).81- Test through public interfaces only — don't peek at private state or internal calls.82- If a test is hard to write, the design is probably wrong — fix the design, not the test.83- Test-after is acceptable for: UI wiring, framework glue, trivial getters/setters.8485## Test Pyramid (default targets)8687- ~70% unit tests — fast, isolated.88- ~20% integration tests — real dependencies where it matters (DB, HTTP).89- ~10% end-to-end — only the critical user paths.9091## Anti-patterns9293- Writing tests after the code "to get coverage" — defeats the design feedback loop.94- Mocking everything — tests pass but the system is broken. Mock at boundaries only (external APIs, time, randomness).95- One giant test asserting many things — failures become hard to diagnose.96- Asserting on internal call counts or order — couples the test to implementation.9798## When invoked as a Builder for a multi-round project99100If the parent agent dispatched you with a `tdd-rounds`-shaped brief listing "Skills (in order): design, tdd, simplify": **execute all three sequentially in this single invocation. Do not return to the parent between skills.** "In order" means run them in that order WITHIN this run — not handed off across separate invocations. Returning early after only `design` is the most common Builder failure mode and forces the parent to re-dispatch.101102The `tdd-rounds` skill captures the full orchestration pattern (Builder brief schema, structured report shape, parent verification ritual). Read it if your brief references it.103104## Pairing with other skills105106- **[`feature-doc`](../feature-doc/SKILL.md)** — runs *before*. ACs become the test list.107- **[`debug`](../debug/SKILL.md)** — runs *before* for non-trivial bugs. Reproduction → failing test.108- **[`design`](../design/SKILL.md)** — runs *alongside*. If TDD feels painful, the design is wrong.109- **[`simplify`](../simplify/SKILL.md)** — runs *after green*. End-of-slice / end-of-round sweep.110- **[`prod-ready`](../prod-ready/SKILL.md)** — runs *after green* (single feature) before opening the PR.111- **[`tdd-rounds`](../tdd-rounds/SKILL.md)** — wraps `tdd` for multi-round projects.112113## Done when114115- All ACs from the feature doc are green and the tests are committed.116- Tests assert behavior through public interfaces, not internals.117- The simplify pass has run.118- For single-feature flow: `prod-ready` is queued. For `tdd-rounds`: the structured Builder report is emitted.119120## Handoff121122When all acceptance criteria are green:123- For a single-feature project: run `prod-ready` before opening the PR. Catches operational, infrastructure, and consistency issues tests don't surface.124- For a multi-round project orchestrated under `tdd-rounds`: emit the structured Builder report (per `tdd-rounds/templates/builder-report.md`) and stop. The parent runs `prod-ready` and `verify-real-deps` at the project level.