← All skills
Rewrite · 5 sources citedProtocolSoftware & games

test-driven-development

Drive a feature, bug fix, refactor or behavior change with strict red-green-refactor - write one failing test, watch it fail for the right reason, write the least code that passes, then clean up while green.

Use it when
Before writing any implementation code, when fixing a bug, or when the user says "TDD", "test first" or "red-green-refactor", or asks why a passing test proves nothing.
What kind it is
A written method: it lays out how the work is done and what to check along the way.
Size
2,023 words. The whole text loads when the skill fires; only the 63-word description is always loaded.
Starts with
Ask for the job it describes.

How it is organised

What’s inside

The skill’s top-level sections, in order. The bar is each section’s share of the 2,023 words, so you can see where the weight sits before reading.

  1. 1The rule236 words
  2. 2The cycle643 words
  3. 3When a failing test is the wrong tool101 words
  4. 4Bug fixes start with the test87 words
  5. 5Working from the outside in139 words
  6. 6Excuses you will hear yourself make241 words
  7. 7Stop and start over if you notice85 words
  8. 8When stuck76 words
  9. 9Before you say it is done229 words
  10. 10Delegated work39 words

The skill itself

Read it before you install it

test-driven-development/SKILL.md · 2,023 wordsDownloadSkill on GitHub ↗
---
name: test-driven-development
description: Drive a feature, bug fix, refactor or behavior change with strict red-green-refactor - write one failing test, watch it fail for the right reason, write the least code that passes, then clean up while green. Use before writing any implementation code, when fixing a bug, or when the user says "TDD", "test first" or "red-green-refactor", or asks why a passing test proves nothing.
license: MIT
---

# Test-driven development

Write the test first. Watch it fail. Write the least code that makes it pass. Clean up while it stays green.

**Why the failure matters.** A test you never saw fail is a test you never saw work. It may assert nothing, call the wrong function, sit in a file the runner skips, or pass because of a typo. Watching it go red is the only evidence that it can detect the absence of the thing you are about to build, and detecting absence is the whole job.

## The rule

```
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
```

Wrote code before the test? Set it aside and start from the test. Do not keep it open as a reference, adapt it while writing the test, or glance at it to remember its shape. Code written first fixes the shape you already imagined, and the test then gets written to fit it. That is how a suite ends up asserting what the code does instead of what the feature needs. The rewrite is short, because you still remember the problem. What you lose is only the bias.

Delete only code you wrote yourself in this session. Never delete, reset or overwrite the user's own work to recreate the right order. If their implementation already exists, add the missing test and show it failing against the original behavior in an isolated copy or a change you can reverse, and restore their code afterwards. If you cannot show a red, say so plainly: "no test-first evidence for this change". Do not claim it.

**Applies to:** new features, bug fixes, refactors, any change in behavior.
**Ask the user before skipping for:** throwaway prototypes, generated code, pure configuration.

Never skip silently. "Just this once", "the test is obvious", "I'll add tests right after" and "it is too small to test" are the thoughts this rule exists to catch. The last one is how most untested code enters a repository.

## The cycle

### 1. Red: one failing test

Write one test for one behavior. Name it so the failure output alone says what broke: `retries a failed operation three times`, not `retry works`. If the name needs "and", split it.

Test real code. A test built on mocks proves that your mocks were called, which is a fact about the test and not about the system. Mock only what you genuinely cannot run: the network, a paid API, the clock. Never mock code you own and could simply call.

Before you write it, say which change to production code would make this test fail. If you cannot name one, the test is not ready.

Good: calls the real function, one behavior, the name states it.

```typescript
test('retries a failed operation three times', async () => {
  let attempts = 0;
  const operation = () => {
    attempts++;
    if (attempts < 3) throw new Error('fail');
    return 'success';
  };

  const result = await retryOperation(operation);

  expect(result).toBe('success');
  expect(attempts).toBe(3);
});
```

Bad: vague name, and it checks the mock rather than the behavior.

```typescript
test('retry works', async () => {
  const mock = jest.fn()
    .mockRejectedValueOnce(new Error())
    .mockRejectedValueOnce(new Error())
    .mockResolvedValueOnce('success');
  await retryOperation(mock);
  expect(mock).toHaveBeenCalledTimes(3);
});
```

### 2. Verify red: run it and read the output

Mandatory. Run the test, then ask: **did it fail for the right reason?**

- Right: an assertion fails, or the function you are about to write is not defined.
- Wrong: a typo, an unresolved import, a missing fixture, a syntax error. These tell you nothing about the feature. Fix the test and run it again. You are not at red yet.
- The test passes: it is testing behavior that already exists. Fix the test.

Make one change and run the test, so the failure message always has one cause. If you made two changes before running, you no longer know which one it is reacting to.

### 3. Green: the least code that passes

Write the smallest code that turns the test green. Not the general version, not the configurable version, not the version that handles what the next test will cover. Minimal keeps every line traceable to a test that demanded it.

Good: just enough to pass.

```typescript
async function retryOperation<T>(fn: () => Promise<T>): Promise<T> {
  for (let i = 0; i < 3; i++) {
    try {
      return await fn();
    } catch (e) {
      if (i === 2) throw e;
    }
  }
  throw new Error('unreachable');
}
```

Bad: options nobody asked for.

```typescript
async function retryOperation<T>(
  fn: () => Promise<T>,
  options?: { maxRetries?: number; backoff?: 'linear' | 'exponential'; onRetry?: (attempt: number) => void }
): Promise<T> { /* speculative */ }
```

Then run the **whole** suite, not just the new test. A local green with regressions elsewhere is a red. The output should be clean: no errors, no warnings.

If the test fails, fix the code, not the test. The one exception is a test that is itself wrong. Go back to red, say why it was wrong, and rewrite it. Never edit a test only to make it pass.

### 4. Check the green is real (the revert check)

For any test you did not watch go red, or when a green arrives suspiciously early:

1. Revert the implementation. The test must go red.
2. Restore it. The test must go green.

A test that stays green with the implementation removed is testing nothing.

### 5. Refactor: only while green

Remove duplication, improve names, extract helpers. Run the tests after each change. If they go red, the refactor changed behavior: revert and take smaller steps. Do not add behavior and do not mix a behavior fix into a refactor. Do them as separate passes.

### 6. Repeat

Take the next behavior to a new failing test. Do not write a second test until the first has finished its full cycle.

## When a failing test is the wrong tool

A failing test drives changed behavior. Two other cases need a different proof, and inventing a failure for them is theater.

- **A refactor that must not change behavior.** Establish passing coverage of the current behavior first (characterization tests if none exist), refactor, and keep it green. Do not manufacture a red.
- **Docs, formatting, and config that does not affect behavior.** Use the check that fits: a diff, a parser or schema validation, a render, a link check. A config change that does affect runtime behavior still needs a behavioral check.

Say which case you are in before you start.

## Bug fixes start with the test

A bug fix begins with a test that reproduces the bug and fails because the bug exists. That one test is the proof the bug was real, the proof the fix works, and the guard against it coming back. Fixing first and adding the test afterwards forfeits all three, because you never saw the test fail and so never confirmed it can detect the bug. When the cause is unknown, find it first (see `systematic-debugging`), then take over here once you can make it fail on demand.

## Working from the outside in

For a feature with several layers, start with one high-level test that describes the behavior as the user sees it, and let each failure choose your next move. When a failure points to a layer that has behavior of its own (an endpoint, a function, a query), drop down and write a failing test at that layer before building it. Drive that test through the cycle. When it goes green, go back up and rerun the test above it. Stop when the top-level test is green and nothing is left waiting.

Things with no behavior of their own, such as a route line, an empty class that clears a "not defined" error, or trivial markup, need no test of their own. Add them only because a failing test one layer up demanded them. When in doubt, write the test.

## Excuses you will hear yourself make

| Excuse | Reality |
|---|---|
| "Too simple to test" | Simple code breaks, and the test takes thirty seconds. |
| "I'll test after" | A test written after passes immediately, which proves nothing. You never saw it catch anything. |
| "Tests after achieve the same goal" | Tests-after answer "what does this do?". Tests-first answer "what should this do?". After the fact you check the cases you remembered, not the ones you would have found. |
| "I already tested it by hand" | Manual testing leaves no record, cannot be rerun, and skips cases under pressure. "It worked when I tried it" is not coverage. |
| "Deleting hours of work is wasteful" | That time is spent either way. The real choice is rewriting with confidence or keeping code you cannot trust. The waste is the second one. |
| "Keep it as reference" | You will adapt it, and that is testing after. |
| "I need to explore first" | Fine. Treat the exploration as a spike, throw it away, then start with a test. A spike is a sketch, never the shipped diff. |
| "The test is hard to write" | Hard to test means hard to use. Simplify the interface. |
| "TDD slows me down" | Finding bugs before the commit is faster than finding them in production. |
| "This is different because..." | It is not. |

## Stop and start over if you notice

- Code exists before its test, or a test was added "later".
- The test passed on the first run and you never saw it fail.
- You cannot say why the test failed.
- A test was edited to force a pass.
- You are keeping code-first work "as reference".
- You are arguing that this case is the exception.

Every one of these means the same thing: set the code-first work aside, return to red, and run the test to confirm it fails.

## When stuck

| Problem | What to try |
|---|---|
| Do not know how to test it | Write the API you wish existed, then write the assertion first. |
| The test is too complicated | The design is too complicated. Simplify the interface. |
| You have to mock everything | The code is too tightly coupled. Inject the dependency. |
| The setup is huge | Extract helpers. Still huge? Simplify the design. |

## Before you say it is done

Claim completion only with fresh evidence from this turn:

| Claim | What actually proves it |
|---|---|
| "The tests pass" | You ran the full suite after your last edit and read the count. |
| "The build is fine" | The build command exited 0. A clean linter is not a build. |
| "This is done" | The behavior was exercised, not only type-checked. |
| "The fix works" | A test that failed before the fix and passes after. |
| "Nothing else broke" | The full suite, not the file you touched. A task that names one test file limits what you deliver, not what you verify. |

Checklist:

- [ ] Every new function or method has a test.
- [ ] You watched each test fail before implementing, and each failed for the right reason.
- [ ] Each implementation is the minimum that passes.
- [ ] The full suite passes with clean output.
- [ ] Tests use real code, with mocks only where unavoidable.
- [ ] Edge cases and error paths are covered.

Report every failure the full run shows, including ones you did not cause, by name. A red test you watched scroll past and did not mention makes your report false by omission. Cannot tick every box? You skipped a step. Go back to it.

## Delegated work

If you hand implementation to another agent or tool, the rule binds it too. An implementer that returns code without a test it watched fail has not finished, and its claim is unverified until you run the suite yourself.

The second audit

What we found in the sources

It keeps the discipline of the version the others copy (obra/superpowers) and adds what the better variants found missing. Every source below was read at the commit the auditor recorded. Nothing was taken from a source without a permissive license.

What each source contributed

  • obra/superpowers ↗ · Primary basis · MIT

    The core rule ("no production code without a failing test first"), the red-green-refactor cycle with good and bad examples, the verify-red and verify-green steps, the excuses table, the red-flags list, the checklist and the "when stuck" table. Every other source in this family reads as a copy, translation, adaptation or rewrite of it.

  • leobbaroni/cockpit ↗ · Added ideas · MIT

    Why the failure matters, the "right reason" test for a red, the bug-fix-starts-with-a-test argument, the table of what actually proves a claim, and the rule that the iron law binds delegated work.

  • j0hanz/squad-agents ↗ · Added ideas · MIT

    The revert check (revert the implementation, expect red, restore, expect green), spikes must be thrown away and rebuilt through the cycle, never edit a test only to force a pass, and one full cycle before the next test.

  • thoughtbot/rails-consultant ↗ · Added ideas · MIT

    Outside-in development: one change then one run, drop down a layer by writing the next failing test, and build inert glue directly. Kept framework-neutral; the Rails and RSpec specifics were left out.

  • lsy041015/orchestra ↗ · Added ideas · MIT

    The safety rule against deleting the user's own work, "report the evidence gap instead of claiming test-first", and the two cases where a failing test is the wrong tool (behavior-preserving refactors, docs and config).

What changed

  • The description now says what the skill does and lists the phrases that should trigger it ("TDD", "test first", "red-green-refactor"). The upstream description was only "Use when implementing any feature or bugfix, before writing implementation code".
  • Rewritten as one self-contained file with no references to companion files, other skills in a bundle, or a particular agent harness or tool.
  • "Delete the code written first" became "set it aside; delete only what you wrote this session; never delete the user's own work". One source (lsy041015/orchestra) flags that the literal rule destroys real work.
  • Replaced the Graphviz diagram with the numbered cycle, and the pseudo-tags around examples with plain Good and Bad labels, so it reads the same in any host.
  • Added the revert check, the when-a-failing-test-is-the-wrong-tool section, and "report pre-existing failures by name" (the last from the VictorStochero/fluxo-orca fork of upstream).
  • Replaced "your human partner" with "the user".

Problems found in the sources

  • obra/superpowers: The description gives only the trigger, so a model matching on "TDD" or "test first" has little to match. The skill refers to writing-good-tests.md, a file that exists only in the original repository, so a copy of the lone SKILL.md has a dangling reference. It uses a Graphviz block and <Good>/<Bad> pseudo-tags that most hosts do not render, and "your human partner" assumes a persona.
  • gylove1994/superpowers-deepseek-v4: Adds a HARD-GATE that tells the agent to invoke a skill (confirming-worktree-before-edit) which is not part of this skill. Without that other skill present, the gate cannot be satisfied.
  • leobbaroni/cockpit: Refers to sibling skills (diagnosing-bugs, orchestrate) that exist only in its own repository.
  • j0hanz/squad-agents: Depends on an AskUserQuestion tool and a request-plan skill, so it is not portable outside its harness.
  • thoughtbot/rails-consultant: Rails and RSpec specific, links to example files that are not in the skill folder, and sets disable-model-invocation so it never triggers on its own.
  • lsy041015/orchestra: Strong ideas but compressed into dense paragraphs and tied to its own orchestra: namespace for the skills it refers to.

What was dropped

  • The Rails and RSpec specifics and the example-file references from thoughtbot/rails-consultant.
  • The long "why order matters" essay, which repeats the excuses table; its points are kept in the table.
  • Interview-the-user steps from j0hanz/squad-agents, which depend on a specific question tool.
  • The upstream reference to writing-good-tests.md. Its four rules are folded into the cycle (name the change that would fail the test, assert real behavior, keep test-only code out of production, understand side effects before mocking).
Read but not used (7)
  • VictorStochero/fluxo-orca: A copy of upstream with one paragraph added, on running the project's whole suite. That paragraph is used.
  • gylove1994/superpowers-deepseek-v4: A variant of upstream (82% similar) with a worktree hard-gate and the "why order matters" essay restored.
  • jnmetacode/superpowers-zh: A Chinese translation of upstream.
  • leodorareluctant259/superpowers-zh: A Chinese translation of upstream.
  • mzored/superpowers-cc-to-codex: A port that hands implementation to a Codex tool; tied to that tool.
  • sitien173/superpowers-ccg: A very short summary that also separates behavior-preserving refactors from changed behavior. The same point is made by orchestra and is used.
  • thepragmaticbear/bearpaws: The upstream text recast in XML tags and shortened; no new ideas.
Left out for license (6)

No license is declared on these, so nothing was taken from them: Athyrr/aikit, Fintama/swisper-superpowers, FlameMida/spec-dev, Itzalive/resistance-ai, lukekeith/builder, orkeren21/shipshape.

Same text, wherever you work

Use it where you work

Copy the folder into your skills directory, then ask for the job or type its start command.

git clone https://github.com/nikbearbrown/skills
cp -R skills/skills/test-driven-development ~/.claude/skills/

Pairs with