skills/test-driven-development/SKILL.md
Drives implementation test-first through red-green-refactor cycles: one failing test, the minimal code to pass it, then cleanup under green. Use before writing production code for a new behavior, and whenever the request mentions TDD, test-driven, test-first, red-green-refactor, "write the test first", "start with a failing test", or reproducing a bug with a test before fixing it. Also use when implementation keeps landing before anyone knows how it will be verified.
npx skillsauth add jaktestowac/awesome-copilot-for-testers test-driven-developmentInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Use this skill when tests should drive the design instead of documenting it afterwards. It keeps the work in small, verified steps: one failing test, one minimal implementation, one cleanup, repeat.
This skill is framework-agnostic. It does not assume a runner, an assertion library, or a mocking tool. Detect what the project already uses before the first cycle; if nothing exists yet, ask which runner to target before writing a test.
Trigger phrases: "TDD", "test-driven", "test-first", "red-green-refactor", "write the test first", "start with a failing test", "make it fail, then make it pass", "reproduce it with a test before fixing".
Typical situations:
Reach for this skill before writing production code, not after. If tests are being added to code that already exists, use writing-unit-tests instead.
TDD is a design technique that produces tests as a by-product - not a coverage technique. The loop's value is the pressure it puts on the interface: code that is hard to test first is usually code that is hard to use.
Judge a session by whether the design got clearer and every change was verified, not by the coverage number it produced.
A seam is the public boundary the test observes behavior through - the interface a real caller would use. Tests live at seams, never against internals.
Every cycle starts by knowing which seam it drives. If you cannot name the seam, you are not ready to write the test.
Seams are agreed, not assumed. Name the seams you intend to test at and get them confirmed before the first test exists. Testing effort is finite; agreeing the seams up front is how it lands on the critical paths and the complex logic instead of on every edge case. Do not write a test at a seam nobody has confirmed.
| Situation | Loop shape | | --- | --- | | A single unit's logic, interface already known | Inner loop only - unit-level red-green-refactor | | A feature crossing several units | Double loop - one failing acceptance test held red on the outside, inner unit cycles until it goes green | | The interface itself is the open question | Write the call you wish existed as the first test, and let it define the shape | | Behavior depends on a real boundary (DB, HTTP, browser) | Drive the owned logic with unit cycles; verify the boundary separately in integration coverage |
In the double loop, the outer test stays red for several inner cycles. Say so explicitly, so a red suite is not mistaken for broken work.
Never drive the loop with a slow test. Red-green pays for itself only while the feedback is fast. A browser or end-to-end test as the cycle signal costs minutes per iteration, and looping on one for a feature that does not exist yet reliably ends in concluding the test is broken. Drive with the fastest thing that can observe the behavior; in a double loop the outer acceptance test is the cheapest test that proves the feature, not the heaviest. Browser coverage is written after the behavior works.
Before the first cycle, decide whether this change deserves the loop at all - ./resources/tdd-fit-check.md has the decision table and what to verify with when the answer is no.
Before the first cycle, establish:
If the interface is genuinely unknown, that is fine: write the call you wish existed in the first test and let it define the shape. Do not silently invent requirements the user has not stated.
Then detect what the project already does, so the loop matches it instead of importing habits from elsewhere:
AGENTS.md, CLAUDE.md, .github/copilot-instructions.md, ADRs - and the domain vocabulary they establish, so test names and interface terms match itThe single-test command matters more than it looks: the loop only works while observing red and green is cheap. Do not introduce a new runner, library, or folder layout unless the user asks for it.
List the behaviors to build, in order, smallest and most central first. This is a queue, not a batch: it records what is coming, it does not authorize writing those tests yet.
Sequence the list so that each entry:
Start with the simplest case that is still interesting - usually a degenerate or empty input - then work outward to the general case.
Keep the list visible and update it as cycles reveal new cases. Use ./resources/tdd-cycle-log-template.md when the session runs long enough to need a record.
Repeat for one entry at a time. ./resources/worked-example.md shows several consecutive cycles in full.
Get to green as fast as possible. Ugly is fine here; the refactor step is where it gets cleaned up.
Pick the strategy that fits your confidence:
| Strategy | Use when | What you write | | --- | --- | --- | | Obvious implementation | You know exactly how it works and it is small | The real implementation, directly | | Fake it | You are unsure, or want the loop green quickly | A hardcoded constant that satisfies this test | | Triangulation | A faked value needs generalizing | A second test with different data, which forces the real logic |
Then:
Hardcoding is legitimate - the next test is what forces generalization. What is not legitimate is leaving a fake in place with no test on the list that will remove it.
Doubles only at real boundaries. Substitute what you do not own - the clock, randomness, the network, storage. If reaching green pushes you to mock a module you do own, the seam is in the wrong place: stop and say so rather than mocking your way to green. A test held up by stubs of your own code proves the stubs were called, freezes the current design, and breaks on the next refactor.
See ./resources/green-step-strategies.md for how to choose between them and how to triangulate deliberately, and the test-doubles guide in writing-unit-tests for the double taxonomy and the boundary rule.
With everything green, look for these in order, in both the production code and the tests:
Rules:
Refactoring is optional per cycle but not optional overall. Skipping it repeatedly is how a green suite ends up guarding a mess - and it is the step that quietly disappears first, because the next test is always more interesting than cleaning up the last one. At session close, state whether it happened. If it consistently does not, hand the cleanup to code-review as an explicit follow-up instead of leaving it implied.
Then take the next entry from the list.
If two or three attempts do not reach green:
Never leave the suite red at the end of a work session without saying so explicitly.
For a defect, the cycle starts with reproduction.
Two conditions change the procedure:
writing-unit-tests has the reproduction configurations and the symptom-to-cause table.Stage the commits so history reads red then green: the failing repro lands first, the fix on top. A reviewer can then replay the bug and its resolution instead of taking the fix on trust. Keep the regression test focused - no unrelated fixture churn riding along.
If no correct seam exists - the bug can only be reproduced through a path nothing can drive in a test - that is itself the finding. Say so, fix the bug, and flag the missing seam as a design problem rather than pretending a shallow test covers it.
Never fix first and test afterwards. A test written after the fix has never been seen catching the bug.
To change code that has no tests, get a safety net before the loop starts:
See the legacy phase in writing-unit-tests for how to write those characterization tests.
TDD is not the right tool for everything. Step out and say so when:
Say which of these applies instead of forcing a unit-shaped test onto the problem.
Stepping out is not skipping verification. Name the closest check you can actually execute - a targeted script, a manual reproduction command, a log or output comparison, an existing focused integration test, a type or startup check for wiring - run it before and after the change, and report its output in place of the red-green pair. Deciding a failing test is impractical is a legitimate call; reporting only the fix, with no substitute check named, is not.
./resources/tdd-fit-check.md holds the decision table, the bad-test definition, and the fallback checks.
Before declaring the work finished:
Report the evidence, not just the outcome:
"It works and the tests pass" is not a report - it names no check that was ever seen failing.
./resources/tdd-fit-check.md - whether the change deserves the loop, what a bad test is, and which check to run when it does not./resources/worked-example.md - consecutive cycles in full, in neutral pseudocode./resources/green-step-strategies.md - fake it, obvious implementation, triangulation, step size, and the revert protocol./resources/tdd-cycle-log-template.md - test list plus per-cycle record of red, green, and refactor stepstest-driven-development-quick - the compact version, for routine single-behavior cycles that do not need the full workflowwriting-unit-tests - the quality standard each test produced by the loop must meet, and the characterization-test procedure for legacy codeunslop-tests - the audit for tests that already exist, including the ones the loop produced under time pressuredesigning-test-data - when the inputs and boundary values need deliberate design before the cycles startdesigning-functional-tests - when the behavior belongs in functional or end-to-end coverage insteadcode-review - for the deeper structural cleanup that does not belong inside the loopThis skill is complete when:
testing
Tests the customization assets themselves - skills, prompts, custom agents, instructions - the way a product is tested: activation cases that check an asset fires when it should and stays quiet when it should not, output-contract cases, safety cases, collision cases between assets competing for the same trigger, a weighted rubric scored blind, and a baseline-versus-candidate gate before an edit ships. Use when a skill is edited and nobody knows whether behaviour changed, when two skills fight over the same request, when a description is being tuned for discoverability, when a collection has grown past manual spot-checking, or when the request mentions skill evals, prompt regression, or "does this skill actually work".
development
Shapes QA output for the person who has to act on it: result and blocker in the first two lines, one decision per report, findings ordered by what they cost, the long artifact in a file and the decisions in the message, and magnitude stated in units the reader can count. Use when a report is accurate but nobody acts on it, when a finding set is too long to read under time pressure, when the same findings must be retold for a developer, a release manager, and an on-call engineer, or when the request mentions "too long", "make this readable", "just tell me what to do", "so what", or "summarize this for stakeholders". Pairs with unslop-answers, which makes the same report honest.
testing
Verifies that the lines and branches a change actually touched are executed by tests, using LCOV or Cobertura diff coverage instead of whole-repo percentages, and escalates uncovered high-risk changes into a blocking finding. Use when a pull request needs a coverage gate that unrelated tests cannot satisfy, when total coverage looks healthy but the diff is untested, when wiring diff coverage into CI, or when someone claims a change is covered because the suite is green.
development
Cuts AI tells from test code: tests that pass without proving anything, tautological assertions, mock-only tests, hardcoded waits, coverage theater, vague names, swallowed errors, retries used as fixes. Use whenever test code is written, changed, or reviewed, including tests produced as a side effect of a feature task, and when the request mentions "review these tests", "are these tests any good", "this test always passes", "this suite is flaky", or "clean up these tests". Must always apply to test code.