CodingDeveloper skill

Test-Driven Development

Red-green-refactor discipline: write the failing test first, make it pass with minimal code, then clean up.

What it does

The normal order is to build something and then, if there is time, write tests for it. Tests written that way pass the moment you run them, which feels reassuring and proves very little, because nobody ever saw them catch anything.

With this skill active, the assistant reverses the order. It writes one small test describing what should happen, runs it, and shows you it failing for the right reason. Only then does it write code, and only enough to make that test pass. Then it tidies up while the test keeps it honest, and repeats. For a bug, the first step is a test that reproduces the report, so there is proof the fix actually fixed something.

You end up with working code and a set of tests that have each been seen to fail once, which is the difference between a safety net and a decoration.

You end up with the feature working and a test for each behaviour that was seen to fail before it passed.

When to reach for it

Use it for any feature or bugfix where whether it works can be checked by running something. It pays for itself fastest on rules with edge cases (pricing, permissions, validation, dates) and on bugs that have come back before.

Skip it for throwaway scripts, one-off data fixes, and configuration files. Be honest about which category the work is really in, because "just this once" is how the habit dies.

What's inside

test-driven-development/
├── SKILL.md
└── references/
    ├── examples.md
    └── anti-patterns.md
SKILL.md
---
name: test-driven-development
description: Drives a feature or bugfix with the red-green-refactor loop. Write one failing test that captures the requirement, watch it fail for the right reason, write the least code that makes it pass, then clean up while it stays green. Use before writing implementation code for a feature, and before fixing any reported bug.
---

# Test-Driven Development

You are the engineer who writes the test first and watches it fail. The failing test is what proves the test works; code written before it has nothing holding it honest.

**Core principle:** if you did not watch the test fail, you do not know that it tests anything.

## Workflow

1. **Pick one slice of behaviour.** The smallest piece of the requirement that can be stated as an assertion.
2. **Write one test** that asserts it. Name it after the behaviour, not the function.
3. **Run it and watch it fail.** Read the failure message. It must fail because the behaviour is missing, not because of a typo or a missing import.
4. **Write the least code that makes it pass.** Nothing beyond what this test demands.
5. **Run it and watch it pass**, along with everything else.
6. **Refactor while green.** Names, duplication, structure. Run again after each step.
7. **Repeat** for the next slice until the requirement is covered, then run the whole suite.

```text
Cycle checklist
[ ] One behaviour, one test, named as a requirement
[ ] Test run before implementation and seen to FAIL
[ ] Failure message is the expected one, not a typo or import error
[ ] Implementation is the minimum that satisfies the test
[ ] Test run again and seen to PASS
[ ] Neighbouring tests still pass, output clean
[ ] Refactor done while green, tests rerun after
[ ] Full suite green before calling the work done
```

## Red: write the failing test

One test, one behaviour. If the name needs the word "and", it is two tests.

```python
def test_retries_a_failed_request_three_times():
    attempts = []

    def flaky():
        attempts.append(1)
        if len(attempts) < 3:
            raise ConnectionError("reset")
        return "ok"

    assert call_with_retry(flaky) == "ok"
    assert len(attempts) == 3
```

The test says what should happen, in the language of the requirement. It uses real code rather than a mock of the thing under test, so it can actually catch a mistake.

## Verify red: watch it fail

Not optional, and not something to assume.

```
$ pytest tests/test_retry.py::test_retries_a_failed_request_three_times
E   NameError: name 'call_with_retry' is not defined
```

Read the message before continuing:

- **Fails with the expected assertion or a missing symbol.** Good, continue.
- **Passes immediately.** The behaviour already exists, or the test asserts nothing. Find out which before writing any code.
- **Errors for an unrelated reason.** A typo, a bad import, a broken fixture. Fix that and rerun until it fails for the right reason.

## Green: the least code that passes

```python
def call_with_retry(fn, attempts=3):
    for attempt in range(1, attempts + 1):
        try:
            return fn()
        except ConnectionError:
            if attempt == attempts:
                raise
```

No backoff, no jitter, no configurable policy, no logging hooks. The current test does not ask for them. If the next requirement does, the next test will drive it.

## Verify green: watch it pass

```
$ pytest tests/test_retry.py -q
2 passed
```

Confirm three things: the new test passes, the neighbours still pass, and the output is clean, with no new warnings and no stack traces printed on the way through.

If the test still fails, fix the code. Do not adjust the assertion to match what the code happens to do.

## Refactor: clean up while green

Only now. Improve names, remove duplication, extract a helper if two places genuinely need it. Rerun after each change. Behaviour does not change during a refactor; if it does, that is a new test first.

## Bug fixes

A bug report is a missing test, so write it before the fix:

1. Turn the report into a test that reproduces the failure.
2. Run it. It must fail, and the failure must look like the report.
3. Fix the cause.
4. Run it. It passes.
5. Keep the test. It is now the regression guard.

A fix without a failing test first is a fix nobody can prove. Confirm the test fails against the unfixed code. A regression test that passes both ways is decoration.

## Example

**Requirement.** `slugify("  Hello   World! ")` returns `"hello-world"`.

| Step | What happened |
| --- | --- |
| Red | `assert slugify("  Hello   World! ") == "hello-world"` fails, `slugify` undefined. |
| Green | Lowercase, strip, collapse whitespace to hyphens, drop punctuation. Passes. |
| Red | Next slice: `slugify("Café")` should not lose the accent handling. Fails. |
| Green | Normalise unicode before stripping. Both tests pass. |
| Refactor | Extract the punctuation set to a module constant. Rerun and both still pass. |

Two cycles, two tests, and an implementation that never grew a feature nobody asked for.

## Rules

- No implementation code without a failing test for it. If code was written first, delete it and drive it from a test rather than adapting it. Adapted code is tested after, whatever the order of the files says.
- One behaviour per test. Split anything whose name contains "and".
- Test names read like requirements: `test_rejects_an_empty_email`, not `test_validation_2`.
- Watch every test fail before you make it pass.
- Never weaken an assertion to get to green. If the test is genuinely wrong, fix the test as its own change with its own reasoning.
- Assert on behaviour and observable output, not on internal calls. A test that asserts a mock was called proves the mock works.
- Use real objects unless a dependency is slow, non-deterministic, or costs money.
- Keep the test output clean. Noise is where real failures hide.

## Edge cases

- **You cannot see how to test it.** Write the test you wish you could (the API you would want to call) and let the difficulty tell you the design is wrong. Hard to test almost always means hard to use.
- **The test needs enormous setup.** That is a design signal too. Extract the piece that has the actual logic and test it directly.
- **Exploratory work where you do not know the shape yet.** Explore freely, then throw the exploration away and rebuild it test-first. Keeping the exploration is how tests-after gets rationalised.
- **Legacy code with no tests.** Write a characterisation test that captures what it does today, confirm it passes, then change behaviour test-first from there.
- **The behaviour is not deterministic.** Time, randomness, network. Inject the clock, seed the random source, or stub the boundary. If none of that is possible, test the layer underneath it.
- **A framework or configuration file.** Not everything is testable and pretending otherwise wastes time. Test the code you wrote, not the framework's.
- **Truly throwaway code.** A one-off script that runs once and is deleted does not need the loop. Be honest about which category the work is really in.

## Files

- `references/examples.md` - three complete cycles with real test output at each step: a new feature over two red-green rounds, a bug fix driven from the report, and a case where the first test passed immediately and what that revealed. Read it for the rhythm and for how much code "minimal" really means.
- `references/anti-patterns.md` - the tests that look thorough and catch nothing: asserting on mocks, test-only methods on production classes, over-mocked suites, and assertions that pass whatever the code does. Read it whenever you are about to add a mock or a test helper.

Install it

Claude Code (this project)

Unzip the download into .claude/skills/. The folder is already named right.

Claude Code (every project)

Unzip into ~/.claude/skills/ to use it everywhere.

claude.ai

Upload the zip under Settings, then Capabilities, then Skills (paid plans).

How to call it

Just describe the task. The description line inside the skill does the matching. Or call it directly:

/test-driven-development write the test first for thisdo this test-drivenadd a test that reproduces this bug before fixing itwhat test would prove this works?red-green-refactor this change

Works beyond Claude

Skills use the open Agent Skills format. The exact same folder works in OpenAI Codex, Gemini CLI, Cursor, GitHub Copilot and other tools that read the format.

Using a plain chat app? Paste SKILL.md into ChatGPT or Gemini as instructions. You lose auto-triggering, but the method still works.

agentskills.io

Pattern credit: obra/superpowers - test-driven-development

Related skills