$npx -y skills add addyosmani/agent-skills --skill test-driven-developmentDrives development with tests. Use when implementing any logic, fixing any bug, or changing any behavior. Use when you need to prove that code works, when a bug report arrives, or when you're about to modify existing functionality.
| 1 | # Test-Driven Development |
| 2 | |
| 3 | ## Overview |
| 4 | |
| 5 | Write a failing test before writing the code that makes it pass. For bug fixes, reproduce the bug with a test before attempting a fix. Tests are proof — "seems right" is not done. A codebase with good tests is an AI agent's superpower; a codebase without tests is a liability. |
| 6 | |
| 7 | ## When to Use |
| 8 | |
| 9 | - Implementing any new logic or behavior |
| 10 | - Fixing any bug (the Prove-It Pattern) |
| 11 | - Modifying existing functionality |
| 12 | - Adding edge case handling |
| 13 | - Any change that could break existing behavior |
| 14 | |
| 15 | **When NOT to use:** Pure configuration changes, documentation updates, or static content changes that have no behavioral impact. |
| 16 | |
| 17 | **Related:** For browser-based changes, combine TDD with runtime verification using Chrome DevTools MCP — see the Browser Testing section below. |
| 18 | |
| 19 | ## Discover the Stack First |
| 20 | |
| 21 | The TDD cycle is universal; the commands are not. Before writing the first test, discover how *this* repository tests, and use its commands for every RED, GREEN, and verification step: |
| 22 | |
| 23 | - **Language and build system** — `package.json`, `pom.xml`/`build.gradle`, `pyproject.toml`, `go.mod`, `Cargo.toml`, `Gemfile`, a `Makefile` |
| 24 | - **Checked-in wrappers** — prefer `./gradlew`, `./mvnw`, `make test`, or a repo script over globally installed tools |
| 25 | - **Test framework and configuration** — and how it runs a single focused test vs the full suite |
| 26 | - **Existing conventions** — where tests live, how files are named, what patterns neighboring tests follow |
| 27 | - **Documented commands** — README, CONTRIBUTING, and CI workflows show the commands that actually gate merges |
| 28 | |
| 29 | Run the repository's focused-test command during the loop and its full-suite command before completion. Never assume a default like `npm test` — a Gradle, Cargo, or pytest project has its own equivalent. |
| 30 | |
| 31 | The examples below use TypeScript for illustration; the workflow is identical in any language once you've discovered the project's own tooling. |
| 32 | |
| 33 | ## The TDD Cycle |
| 34 | |
| 35 | ``` |
| 36 | RED GREEN REFACTOR |
| 37 | Write a test Write minimal code Clean up the |
| 38 | that fails ──→ to make it pass ──→ implementation ──→ (repeat) |
| 39 | │ │ │ |
| 40 | ▼ ▼ ▼ |
| 41 | Test FAILS Test PASSES Tests still PASS |
| 42 | ``` |
| 43 | |
| 44 | ### Step 1: RED — Write a Failing Test |
| 45 | |
| 46 | Write the test first. It must fail. A test that passes immediately proves nothing. |
| 47 | |
| 48 | ```typescript |
| 49 | // RED: This test fails because createTask doesn't exist yet |
| 50 | describe('TaskService', () => { |
| 51 | it('creates a task with title and default status', async () => { |
| 52 | const task = await taskService.createTask({ title: 'Buy groceries' }); |
| 53 | |
| 54 | expect(task.id).toBeDefined(); |
| 55 | expect(task.title).toBe('Buy groceries'); |
| 56 | expect(task.status).toBe('pending'); |
| 57 | expect(task.createdAt).toBeInstanceOf(Date); |
| 58 | }); |
| 59 | }); |
| 60 | ``` |
| 61 | |
| 62 | ### Step 2: GREEN — Make It Pass |
| 63 | |
| 64 | Write the minimum code to make the test pass. Don't over-engineer: |
| 65 | |
| 66 | ```typescript |
| 67 | // GREEN: Minimal implementation |
| 68 | export async function createTask(input: { title: string }): Promise<Task> { |
| 69 | const task = { |
| 70 | id: generateId(), |
| 71 | title: input.title, |
| 72 | status: 'pending' as const, |
| 73 | createdAt: new Date(), |
| 74 | }; |
| 75 | await db.tasks.insert(task); |
| 76 | return task; |
| 77 | } |
| 78 | ``` |
| 79 | |
| 80 | ### Step 3: REFACTOR — Clean Up |
| 81 | |
| 82 | With tests green, improve the code without changing behavior: |
| 83 | |
| 84 | - Extract shared logic |
| 85 | - Improve naming |
| 86 | - Remove duplication |
| 87 | - Optimize if necessary |
| 88 | |
| 89 | Run tests after every refactor step to confirm nothing broke. |
| 90 | |
| 91 | ## The Prove-It Pattern (Bug Fixes) |
| 92 | |
| 93 | When a bug is reported, **do not start by trying to fix it.** Start by writing a test that reproduces it. |
| 94 | |
| 95 | ``` |
| 96 | Bug report arrives |
| 97 | │ |
| 98 | ▼ |
| 99 | Write a test that demonstrates the bug |
| 100 | │ |
| 101 | ▼ |
| 102 | Test FAILS (confirming the bug exists) |
| 103 | │ |
| 104 | ▼ |
| 105 | Implement the fix |
| 106 | │ |
| 107 | ▼ |
| 108 | Test PASSES (proving the fix works) |
| 109 | │ |
| 110 | ▼ |
| 111 | Run full test suite (no regressions) |
| 112 | ``` |
| 113 | |
| 114 | **Example:** |
| 115 | |
| 116 | ```typescript |
| 117 | // Bug: "Completing a task doesn't update the completedAt timestamp" |
| 118 | |
| 119 | // Step 1: Write the reproduction test (it should FAIL) |
| 120 | it('sets completedAt when task is completed', async () => { |
| 121 | const task = await taskService.createTask({ title: 'Test' }); |
| 122 | const completed = await taskService.completeTask(task.id); |
| 123 | |
| 124 | expect(completed.status).toBe('completed'); |
| 125 | expect(completed.completedAt).toBeInstanceOf(Date); // This fails → bug confirmed |
| 126 | }); |
| 127 | |
| 128 | // Step 2: Fix the bug |
| 129 | export async function completeTask(id: string): Promise<Task> { |
| 130 | return db.tasks.update(id, { |
| 131 | status: 'completed', |
| 132 | completedAt: new Date(), // This was missing |
| 133 | }); |
| 134 | } |
| 135 | |
| 136 | // Step 3: Test passes → bug fixed, regression guarded |
| 137 | ``` |
| 138 | |
| 139 | ## The Test Pyramid |
| 140 | |
| 141 | Invest |