Skill Tracer
Skills › Superpowers › test-driven-development

test-driven-development

skill · 330 lines · MIT · skills/test-driven-development/SKILL.md

The facts

Its description

Use when implementing any feature or bugfix, before writing implementation code

The description the agent reads to decide when to use it.

From its settingsline 3

When it starts

The agent picks this skill when the person asks it to build a feature, fix a bug, refactor, or otherwise change behavior, and the work is about to move into writing implementation code — the description scopes it to that moment, before any production code exists.

Things a person might say (the model's guesses)

  • “Add a retry option to the upload function.”
  • “Fix the bug where an empty email is accepted.”
  • “Refactor the form validation to handle several fields.”

A model's summary of when it is used.

A model's reading

Files it mentions

writing-good-tests.md

Files the text names, such as ones to read or write. Found by matching words, so treat them as pointers.

Found by matching wordsline 216

Stops for a person

2 lines stop to ask a person

Lines that stop and wait for a person before going on. Found by matching words, so treat them as pointers.

Found by matching wordsline 24 line 312

What leads to it

Other skills that hand off to this one, call it, require it or offer it instead, as a model read them.

A model's reading

Licence

MIT, from LICENSE

The terms its author shares it under, which let it be shown here.

From the repositorylicence text
Other files in this skill (1) Close

The text

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1---
2name: test-driven-development
3description: Use when implementing any feature or bugfix, before writing implementation code
4---
5
6# Test-Driven Development (TDD)
7

Test-Driven Development (TDD)

Raw lines 1 to 7
8## Overview
9
10Write the test first. Watch it fail. Write minimal code to pass.
12**Core principle:** If you didn't watch the test fail, you don't know if it tests the right thing.
14**Violating the letter of the rules is violating the spirit of the rules.**

Overview

Write the test first. Watch it fail. Write minimal code to pass.

Core principle: If you didn't watch the test fail, you don't know if it tests the right thing.

Violating the letter of the rules is violating the spirit of the rules.

Raw lines 8 to 15
16## When to Use
18**Always:**
19- New features
20- Bug fixes
21- Refactoring
22- Behavior changes
24**Exceptions (ask your human partner):**
25- Throwaway prototypes
26- Generated code
27- Configuration files
29Thinking "skip TDD just this once"? Stop. That's rationalization.

When to Use

Always:

  • New features
  • Bug fixes
  • Refactoring
  • Behavior changes

Exceptions (ask your human partner):

  • Throwaway prototypes
  • Generated code
  • Configuration files

Thinking "skip TDD just this once"? Stop. That's rationalization.

Raw lines 16 to 30
Involves a person ask your human partner :24
31## The Iron Law
33```
34NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
35```
37Write code before the test? Delete it. Start over.
39**No exceptions:**
40- Don't keep it as "reference"
41- Don't "adapt" it while writing tests
42- Don't look at it
43- Delete means delete
45Implement fresh from tests. Period.

The Iron Law

NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST

Write code before the test? Delete it. Start over.

No exceptions:

  • Don't keep it as "reference"
  • Don't "adapt" it while writing tests
  • Don't look at it
  • Delete means delete

Implement fresh from tests. Period.

Raw lines 31 to 46
47## Red-Green-Refactor
49```dot
50digraph tdd_cycle {
51 rankdir=LR;
52 red [label="RED\nWrite failing test", shape=box, style=filled, fillcolor="#ffcccc"];
53 verify_red [label="Verify fails\ncorrectly", shape=diamond];
54 green [label="GREEN\nMinimal code", shape=box, style=filled, fillcolor="#ccffcc"];
55 verify_green [label="Verify passes\nAll green", shape=diamond];
56 refactor [label="REFACTOR\nClean up", shape=box, style=filled, fillcolor="#ccccff"];
57 next [label="Next", shape=ellipse];
59 red -> verify_red;
60 verify_red -> green [label="yes"];
61 verify_red -> red [label="wrong\nfailure"];
62 green -> verify_green;
63 verify_green -> refactor [label="yes"];
64 verify_green -> green [label="no"];
65 refactor -> verify_green [label="stay\ngreen"];
66 verify_green -> next;
67 next -> red;
69```

Red-Green-Refactor

digraph tdd_cycle {
    rankdir=LR;
    red [label="RED\nWrite failing test", shape=box, style=filled, fillcolor="#ffcccc"];
    verify_red [label="Verify fails\ncorrectly", shape=diamond];
    green [label="GREEN\nMinimal code", shape=box, style=filled, fillcolor="#ccffcc"];
    verify_green [label="Verify passes\nAll green", shape=diamond];
    refactor [label="REFACTOR\nClean up", shape=box, style=filled, fillcolor="#ccccff"];
    next [label="Next", shape=ellipse];

    red -> verify_red;
    verify_red -> green [label="yes"];
    verify_red -> red [label="wrong\nfailure"];
    green -> verify_green;
    verify_green -> refactor [label="yes"];
    verify_green -> green [label="no"];
    refactor -> verify_green [label="stay\ngreen"];
    verify_green -> next;
    next -> red;
}
Raw lines 47 to 70
71### RED - Write Failing Test
73Write one minimal test showing what should happen.
75<Good>
76```typescript
77test('retries failed operations 3 times', async () => {
78 let attempts = 0;
79 const operation = () => {
80 attempts++;
81 if (attempts < 3) throw new Error('fail');
82 return 'success';
83 };
85 const result = await retryOperation(operation);
87 expect(result).toBe('success');
88 expect(attempts).toBe(3);
89});
90```
91Clear name, tests real behavior, one thing
92</Good>
94<Bad>
95```typescript
96test('retry works', async () => {
97 const mock = jest.fn()
98 .mockRejectedValueOnce(new Error())
99 .mockRejectedValueOnce(new Error())
100 .mockResolvedValueOnce('success');
101 await retryOperation(mock);
102 expect(mock).toHaveBeenCalledTimes(3);
103});
104```
105Vague name, tests mock not code
106</Bad>
108**Requirements:**
109- One behavior
110- Clear name
111- Real code (no mocks unless unavoidable)

RED - Write Failing Test

Write one minimal test showing what should happen.

<Good>

test('retries failed operations 3 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);
});

Clear name, tests real behavior, one thing </Good>

<Bad>

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

Vague name, tests mock not code </Bad>

Requirements:

  • One behavior
  • Clear name
  • Real code (no mocks unless unavoidable)
Raw lines 71 to 112
113### Verify RED - Watch It Fail
115**MANDATORY. Never skip.**
117```bash
118npm test path/to/test.test.ts
119```
121Confirm:
122- Test fails (not errors)
123- Failure message is expected
124- Fails because feature missing (not typos)
126**Test passes?** You're testing existing behavior. Fix test.
128**Test errors?** Fix error, re-run until it fails correctly.

Verify RED - Watch It Fail

MANDATORY. Never skip.

npm test path/to/test.test.ts

Confirm:

  • Test fails (not errors)
  • Failure message is expected
  • Fails because feature missing (not typos)

Test passes? You're testing existing behavior. Fix test.

Test errors? Fix error, re-run until it fails correctly.

Raw lines 113 to 129
130### GREEN - Minimal Code
132Write simplest code to pass the test.
134<Good>
135```typescript
136async function retryOperation<T>(fn: () => Promise<T>): Promise<T> {
137 for (let i = 0; i < 3; i++) {
138 try {
139 return await fn();
140 } catch (e) {
141 if (i === 2) throw e;
142 }
143 }
144 throw new Error('unreachable');
146```
147Just enough to pass
148</Good>
150<Bad>
151```typescript
152async function retryOperation<T>(
153 fn: () => Promise<T>,
154 options?: {
155 maxRetries?: number;
156 backoff?: 'linear' | 'exponential';
157 onRetry?: (attempt: number) => void;
158 }
159): Promise<T> {
160 // YAGNI
162```
163Over-engineered
164</Bad>
166Don't add features, refactor other code, or "improve" beyond the test.

GREEN - Minimal Code

Write simplest code to pass the test.

<Good>

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');
}

Just enough to pass </Good>

<Bad>

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

Over-engineered </Bad>

Don't add features, refactor other code, or "improve" beyond the test.

Raw lines 130 to 167
168### Verify GREEN - Watch It Pass
170**MANDATORY.**
172```bash
173npm test path/to/test.test.ts
174```
176Confirm:
177- Test passes
178- Other tests still pass
179- Output pristine (no errors, warnings)
181**Test fails?** Fix code, not test.
183**Other tests fail?** Fix now.
185**"Other tests" means the project's suite, not just your file.** A
186green run of the test you wrote is not a green suite. Before you call
187the change done, run the project's test command (bare `pytest`,
188`npm test`, `cargo test` — whatever the repo uses) even when your task
189named only one test file. A scope statement in your task bounds the
190deliverable, not your verification. Any failure that run shows —
191including one you didn't cause — goes in your report by name; a red
192test you watched scroll past and didn't mention is a report falsified
193by omission.

Verify GREEN - Watch It Pass

MANDATORY.

npm test path/to/test.test.ts

Confirm:

  • Test passes
  • Other tests still pass
  • Output pristine (no errors, warnings)

Test fails? Fix code, not test.

Other tests fail? Fix now.

"Other tests" means the project's suite, not just your file. A green run of the test you wrote is not a green suite. Before you call the change done, run the project's test command (bare pytest, npm test, cargo test — whatever the repo uses) even when your task named only one test file. A scope statement in your task bounds the deliverable, not your verification. Any failure that run shows — including one you didn't cause — goes in your report by name; a red test you watched scroll past and didn't mention is a report falsified by omission.

Raw lines 168 to 194
195### REFACTOR - Clean Up
197After green only:
198- Remove duplication
199- Improve names
200- Extract helpers
202Keep tests green. Don't add behavior.

REFACTOR - Clean Up

After green only:

  • Remove duplication
  • Improve names
  • Extract helpers

Keep tests green. Don't add behavior.

Raw lines 195 to 203
204### Repeat
206Next failing test for next feature.

Repeat

Next failing test for next feature.

Raw lines 204 to 207
208## Good Tests
210| Quality | Good | Bad |
211|---------|------|-----|
212| **Minimal** | One thing. "and" in name? Split it. | `test('validates email and domain and whitespace')` |
213| **Clear** | Name describes behavior | `test('test1')` |
214| **Shows intent** | Demonstrates desired API | Obscures what code should do |
216When writing or changing any test, read [writing-good-tests.md](writing-good-tests.md) for the rules that keep tests honest:
217- Name the production change that would make the test fail — before writing it
218- Assert on real behavior, never on mock behavior
219- Keep test-only code in test utilities, out of production classes
220- Understand a dependency's side effects before mocking it

Good Tests

QualityGoodBad
MinimalOne thing. "and" in name? Split it.test('validates email and domain and whitespace')
ClearName describes behaviortest('test1')
Shows intentDemonstrates desired APIObscures what code should do

When writing or changing any test, read writing-good-tests.md for the rules that keep tests honest:

  • Name the production change that would make the test fail — before writing it
  • Assert on real behavior, never on mock behavior
  • Keep test-only code in test utilities, out of production classes
  • Understand a dependency's side effects before mocking it
Raw lines 208 to 221
Reads a file writing-good-tests.md :216
222## Common Rationalizations
224| Excuse | Reality |
225|--------|---------|
226| "Too simple to test" | Simple code breaks. Test takes 30 seconds. |
227| "I'll test after" | Tests written after pass immediately — which proves nothing. They may test the wrong thing, test the implementation instead of the behavior, or miss the edge case you forgot. You never watched it fail, so you never proved it can catch the bug. Test-first forces that failure. |
228| "Tests after achieve same goals (spirit not ritual)" | Tests-after answer "what does this do?"; tests-first answer "what should this do?" Tests written after are biased by the code you already wrote — you verify the cases you remembered, not the ones you'd have discovered. Coverage without proof the tests work. |
229| "Already manually tested" | Manual testing is ad-hoc: no record of what you covered, no way to re-run it when the code changes, easy to forget cases under pressure. "Worked when I tried it" ≠ comprehensive. Automated tests run the same way every time. |
230| "Deleting X hours is wasteful" | Sunk cost fallacy — that time is already spent either way. The real choice: rewrite with TDD (high confidence) vs. keep it and bolt tests on after (low confidence, likely bugs). Keeping code you can't trust is the waste. |
231| "Keep as reference, write tests first" | You'll adapt it. That's testing after. Delete means delete. |
232| "Need to explore first" | Fine. Throw away exploration, start with TDD. |
233| "Test hard = design unclear" | Listen to test. Hard to test = hard to use. |
234| "TDD will slow me down" | TDD IS the pragmatic path: catches bugs before commit, prevents regressions, lets you refactor without fear. "Pragmatic" shortcuts mean debugging in production — slower, not faster. |
235| "Manual test faster" | Manual doesn't prove edge cases. You'll re-test every change. |
236| "Existing code has no tests" | You're improving it. Add tests for existing code. |

Common Rationalizations

ExcuseReality
"Too simple to test"Simple code breaks. Test takes 30 seconds.
"I'll test after"Tests written after pass immediately — which proves nothing. They may test the wrong thing, test the implementation instead of the behavior, or miss the edge case you forgot. You never watched it fail, so you never proved it can catch the bug. Test-first forces that failure.
"Tests after achieve same goals (spirit not ritual)"Tests-after answer "what does this do?"; tests-first answer "what should this do?" Tests written after are biased by the code you already wrote — you verify the cases you remembered, not the ones you'd have discovered. Coverage without proof the tests work.
"Already manually tested"Manual testing is ad-hoc: no record of what you covered, no way to re-run it when the code changes, easy to forget cases under pressure. "Worked when I tried it" ≠ comprehensive. Automated tests run the same way every time.
"Deleting X hours is wasteful"Sunk cost fallacy — that time is already spent either way. The real choice: rewrite with TDD (high confidence) vs. keep it and bolt tests on after (low confidence, likely bugs). Keeping code you can't trust is the waste.
"Keep as reference, write tests first"You'll adapt it. That's testing after. Delete means delete.
"Need to explore first"Fine. Throw away exploration, start with TDD.
"Test hard = design unclear"Listen to test. Hard to test = hard to use.
"TDD will slow me down"TDD IS the pragmatic path: catches bugs before commit, prevents regressions, lets you refactor without fear. "Pragmatic" shortcuts mean debugging in production — slower, not faster.
"Manual test faster"Manual doesn't prove edge cases. You'll re-test every change.
"Existing code has no tests"You're improving it. Add tests for existing code.
Raw lines 222 to 237
238## Red Flags - STOP and Start Over
240- Code before test
241- Test after implementation
242- Test passes immediately
243- Can't explain why test failed
244- Tests added "later"
245- Rationalizing "just this once"
246- "I already manually tested it"
247- "Tests after achieve the same purpose"
248- "It's about spirit not ritual"
249- "Keep as reference" or "adapt existing code"
250- "Already spent X hours, deleting is wasteful"
251- "TDD is dogmatic, I'm being pragmatic"
252- "This is different because..."
254**All of these mean: Delete code. Start over with TDD.**

Red Flags - STOP and Start Over

  • Code before test
  • Test after implementation
  • Test passes immediately
  • Can't explain why test failed
  • Tests added "later"
  • Rationalizing "just this once"
  • "I already manually tested it"
  • "Tests after achieve the same purpose"
  • "It's about spirit not ritual"
  • "Keep as reference" or "adapt existing code"
  • "Already spent X hours, deleting is wasteful"
  • "TDD is dogmatic, I'm being pragmatic"
  • "This is different because..."

All of these mean: Delete code. Start over with TDD.

Raw lines 238 to 255
256## Example: Bug Fix
258**Bug:** Empty email accepted
260**RED**
261```typescript
262test('rejects empty email', async () => {
263 const result = await submitForm({ email: '' });
264 expect(result.error).toBe('Email required');
265});
266```
268**Verify RED**
269```bash
270$ npm test
271FAIL: expected 'Email required', got undefined
272```
274**GREEN**
275```typescript
276function submitForm(data: FormData) {
277 if (!data.email?.trim()) {
278 return { error: 'Email required' };
279 }
280 // ...
282```
284**Verify GREEN**
285```bash
286$ npm test
287PASS
288```
290**REFACTOR**
291Extract validation for multiple fields if needed.

Example: Bug Fix

Bug: Empty email accepted

RED

test('rejects empty email', async () => {
  const result = await submitForm({ email: '' });
  expect(result.error).toBe('Email required');
});

Verify RED

$ npm test
FAIL: expected 'Email required', got undefined

GREEN

function submitForm(data: FormData) {
  if (!data.email?.trim()) {
    return { error: 'Email required' };
  }
  // ...
}

Verify GREEN

$ npm test
PASS

REFACTOR Extract validation for multiple fields if needed.

Raw lines 256 to 292
293## Verification Checklist
295Before marking work complete:
297- [ ] Every new function/method has a test
298- [ ] Watched each test fail before implementing
299- [ ] Each test failed for expected reason (feature missing, not typo)
300- [ ] Wrote minimal code to pass each test
301- [ ] All tests pass
302- [ ] Output pristine (no errors, warnings)
303- [ ] Tests use real code (mocks only if unavoidable)
304- [ ] Edge cases and errors covered
306Can't check all boxes? You skipped TDD. Start over.

Verification Checklist

Before marking work complete:

  • [ ] Every new function/method has a test
  • [ ] Watched each test fail before implementing
  • [ ] Each test failed for expected reason (feature missing, not typo)
  • [ ] Wrote minimal code to pass each test
  • [ ] All tests pass
  • [ ] Output pristine (no errors, warnings)
  • [ ] Tests use real code (mocks only if unavoidable)
  • [ ] Edge cases and errors covered

Can't check all boxes? You skipped TDD. Start over.

Raw lines 293 to 307
308## When Stuck
310| Problem | Solution |
311|---------|----------|
312| Don't know how to test | Write wished-for API. Write assertion first. Ask your human partner. |
313| Test too complicated | Design too complicated. Simplify interface. |
314| Must mock everything | Code too coupled. Use dependency injection. |
315| Test setup huge | Extract helpers. Still complex? Simplify design. |

When Stuck

ProblemSolution
Don't know how to testWrite wished-for API. Write assertion first. Ask your human partner.
Test too complicatedDesign too complicated. Simplify interface.
Must mock everythingCode too coupled. Use dependency injection.
Test setup hugeExtract helpers. Still complex? Simplify design.
Raw lines 308 to 316
Involves a person Ask your human partner :312
317## Debugging Integration
319Bug found? Write failing test reproducing it. Follow TDD cycle. Test proves fix and prevents regression.
321Never fix bugs without a test.

Debugging Integration

Bug found? Write failing test reproducing it. Follow TDD cycle. Test proves fix and prevents regression.

Never fix bugs without a test.

Raw lines 317 to 322
323## Final Rule
325```
326Production code → test exists and failed first
327Otherwise → not TDD
328```
330No exceptions without your human partner's permission.

Final Rule

Production code → test exists and failed first
Otherwise → not TDD

No exceptions without your human partner's permission.

Raw lines 323 to 330

From obra/superpowers at 2ce8aa1, 2026-10-21. Skills read by DeepSeek V4.1 Flash on 2026-11-04. Summary and techniques by Claude Opus 5.5 on 2026-11-06.