Use when implementing a new feature, fixing a bug, or refactoring existing behavior — proven correct by executable tests written before production code.
How this skill is triggered — by the user, by Claude, or both
Slash command
/test-driven-development:test-driven-developmentThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Use this skill when implementing any feature, bugfix, or behavior change that requires confidence that the code works as intended. TDD is the practice of writing a failing test first, then writing minimal production code to pass it, then refactoring.
Use this skill when implementing any feature, bugfix, or behavior change that requires confidence that the code works as intended. TDD is the practice of writing a failing test first, then writing minimal production code to pass it, then refactoring.
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
Write the test. Watch it fail. Write minimal code to pass. Never write production code without a failing test first.
Tests are specifications. They answer "what should this code do?" Tests written after are archaeology; they can only answer "what does this code do?"
| Scenario | Use TDD Skill | Route Away |
|---|---|---|
| New feature request | Yes — write test for the desired behavior first | N/A |
| Bug report | Yes — write test that reproduces the bug, then fix | Consider github-actions-failure-triage if it's a CI failure |
| Refactor request | Yes — write test for current behavior, refactor while keeping tests green | Consider typescript-any-eliminator or other targeted hardening skills for narrower improvements |
| Code review feedback | Yes if feedback requests behavior change or new handling | Use review-comment-resolution for process; route back to TDD for code changes |
| Performance optimization | Yes — write benchmark test first, then optimize, keep tests green | N/A |
| Configuration or docs only | No | Document without this skill |
Before starting, clarify:
The test failure is your proof that the feature does not exist yet. This is the RED phase.
RED: Write one minimal failing test for the specific behavior. Confirm it fails for the right reason (feature missing, not syntax error).
should return 42 when input is valid not should work.GREEN: Write the simplest code that makes the test pass. No extra features, no YAGNI (You Aren't Gonna Need It).
REFACTOR: Clean up only after green. Remove duplication, improve names, extract helpers. Keep tests green.
Repeat for the next behavior.
If production code was written before a test, delete it. Do not "adapt" it while writing tests afterward. Delete means delete.
Unverified code is not a time-saving shortcut; it is technical debt. Keeping it forces you to reverse-engineer what it was supposed to do, which takes longer than rewriting it test-first.
| Rationalisation | Reality |
|---|---|
| "Too simple to test" | Simple code breaks. A test takes 30 seconds to write. |
| "I'll write tests after" | Tests-after answer "what does this do?" Tests-first answer "what should this do?" — Different questions, different results. |
| "Already manually tested" | Manual testing is ad-hoc and unmemoried. No record, can't re-run. Automated tests are proof and documentation. |
| "Deleting X hours of work is wasteful" | Sunk cost. Keeping unverified code is debt that compounds. Rewriting it test-first clears the debt. |
| "TDD will slow me down" | TDD is faster than debugging. A failing test is a precise bug report; console.log is archaeology. |
| "Mocking everything makes tests too complex" | Mock only external dependencies (databases, APIs, files). Test logic with real data structures. |
| "This code is too coupled to test" | That is the signal to refactor. Testability is a design metric. Hard-to-test code is brittle. |
Before marking work complete:
verification-before-completion)Adding a new validator:
Write a failing test: expect(validateEmail('')).toBe(false) → implement validateEmail() until it passes → add edge-case tests for malformed inputs.
Reproducing a bug before fixing it:
Write expect(parseAmount('£1,000.00')).toBe(1000) → confirm it fails → fix parseAmount() → confirm it passes.
Pinning behavior before refactoring: Capture current return values in a test suite → refactor the implementation → verify all tests still pass.
See references/tdd-scenarios.md for full TypeScript-focused walkthroughs.
references/tdd-scenarios.md — Scenario walkthroughs with TypeScript code examples (500–800 words).Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub matt-riley/lucky-hat --plugin test-driven-development