$npx -y skills add Codagent-AI/agent-skills --skill implement-with-tddEnforces test-driven development for feature work, bug fixes, and refactoring, activating for requests such as "implement", "fix", "add feature", "write tests first", or "TDD".
| 1 | # Test-Driven Development (TDD) |
| 2 | |
| 3 | ## Overview |
| 4 | |
| 5 | Write the test first. Watch it fail. Write minimal code to pass. |
| 6 | |
| 7 | **Core principle:** Watch the test fail. Only then is it proven to test the right thing. |
| 8 | |
| 9 | **Violating the letter of the rules is violating the spirit of the rules.** |
| 10 | |
| 11 | ## When to Use |
| 12 | |
| 13 | **Always:** |
| 14 | - New features |
| 15 | - Bug fixes |
| 16 | - Refactoring |
| 17 | - Behavior changes |
| 18 | |
| 19 | **Exceptions (skip TDD only for these; otherwise follow the full TDD cycle):** |
| 20 | - Throwaway spikes that will never be merged (branch must be deleted before implementation begins; any code carried forward requires full TDD) |
| 21 | - Generated code (scaffolded, not behavior-bearing) |
| 22 | - Configuration-only changes |
| 23 | |
| 24 | Thinking "skip TDD just this once"? Stop. That's rationalization. |
| 25 | |
| 26 | ## The Iron Law |
| 27 | |
| 28 | ``` |
| 29 | NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST |
| 30 | ``` |
| 31 | |
| 32 | Write code before the test? Delete it. Start over. |
| 33 | |
| 34 | **No exceptions:** |
| 35 | - Don't keep it as "reference" |
| 36 | - Don't "adapt" it while writing tests |
| 37 | - Don't look at it |
| 38 | - Delete means delete |
| 39 | |
| 40 | Implement fresh from tests. Period. |
| 41 | |
| 42 | ## Choosing Test Type |
| 43 | |
| 44 | Default to unit tests. Escalate only when the thing you're testing can't be isolated: |
| 45 | |
| 46 | - **Unit** — pure logic, validation, transformations, calculations. No I/O, no wiring. Fast (<100ms). |
| 47 | - **Integration** — components wired together: database queries, middleware chains, API clients, config reaching runtime. If the bug would only manifest when components connect, it needs an integration test. |
| 48 | - **E2E** — critical user flows only (login, checkout, onboarding). Expensive, slow, flaky-prone. Don't use for logic that can be tested at a lower level. |
| 49 | |
| 50 | When in doubt: can you test it with a function call and an assertion? Unit test. Does it require spinning up real infrastructure or connecting real components? Integration test. |
| 51 | |
| 52 | ## From Spec to Tests |
| 53 | |
| 54 | Each scenario in the spec becomes one test. Write the test name and assertion from the scenario before writing any implementation. |
| 55 | |
| 56 | **Spec scenario:** |
| 57 | > **WHEN** a user with an expired session requests a protected resource |
| 58 | > **THEN** they receive a 401 and a redirect to login |
| 59 | |
| 60 | **Test:** |
| 61 | ```typescript |
| 62 | test('expired session returns 401 with login redirect', async () => { |
| 63 | const session = createExpiredSession(); |
| 64 | const response = await requestProtectedResource(session); |
| 65 | expect(response.status).toBe(401); |
| 66 | expect(response.headers.location).toBe('/login'); |
| 67 | }); |
| 68 | ``` |
| 69 | |
| 70 | One test per scenario, one scenario per behavior. If a scenario needs multiple assertions, that's fine — but if it needs multiple *setups*, split it. If the spec numbers its criteria (AC-1, R-001, etc.), reference the ID in the test name or comment for traceability. (Note: the spec skill does not generate IDs by default — IDs are an optional custom extension you may add to your spec for traceability.) |
| 71 | |
| 72 | ## Red-Green-Refactor |
| 73 | |
| 74 | ```dot |
| 75 | digraph tdd_cycle { |
| 76 | rankdir=LR; |
| 77 | red [label="RED\nWrite failing test", shape=box, style=filled, fillcolor="#ffcccc"]; |
| 78 | verify_red [label="Verify fails\ncorrectly", shape=diamond]; |
| 79 | green [label="GREEN\nMinimal code", shape=box, style=filled, fillcolor="#ccffcc"]; |
| 80 | verify_green [label="Verify passes\nAll green", shape=diamond]; |
| 81 | refactor [label="REFACTOR\nClean up", shape=box, style=filled, fillcolor="#ccccff"]; |
| 82 | next [label="Next", shape=ellipse]; |
| 83 | |
| 84 | red -> verify_red; |
| 85 | verify_red -> green [label="yes"]; |
| 86 | verify_red -> red [label="wrong\nfailure"]; |
| 87 | green -> verify_green; |
| 88 | verify_green -> refactor [label="yes"]; |
| 89 | verify_green -> green [label="no"]; |
| 90 | refactor -> verify_green [label="stay\ngreen"]; |
| 91 | verify_green -> next; |
| 92 | next -> red; |
| 93 | } |
| 94 | ``` |
| 95 | |
| 96 | ### RED - Write Failing Test |
| 97 | |
| 98 | Write one minimal test showing what should happen. |
| 99 | |
| 100 | **Good:** |
| 101 | ```typescript |
| 102 | test('retries failed operations 3 times', async () => { |
| 103 | let attempts = 0; |
| 104 | const operation = () => { |
| 105 | attempts++; |
| 106 | if (attempts < 3) throw new Error('fail'); |
| 107 | return 'success'; |
| 108 | }; |
| 109 | |
| 110 | const result = await retryOperation(operation); |
| 111 | |
| 112 | expect(result).toBe('success'); |
| 113 | expect(attempts).toBe(3); |
| 114 | }); |
| 115 | ``` |
| 116 | _Clear name, tests real behavior, one thing_ |
| 117 | |
| 118 | **Bad:** |
| 119 | ```typescript |
| 120 | test('retry works', async () => { |
| 121 | const mock = jest.fn() |
| 122 | .mockRejectedValueOnce(new Error()) |
| 123 | .mockRejectedValueOnce(new Error()) |
| 124 | .mockResolvedValueOnce('success'); |
| 125 | await retryOperation(mock); |
| 126 | expect(mock).toHaveBeenCalledTimes(3); |
| 127 | }); |
| 128 | ``` |
| 129 | _Vague name, tests mock not code_ |
| 130 | |
| 131 | **Requirements:** |
| 132 | - One behavior |
| 133 | - Clear name |
| 134 | - Real code (no mocks unless unavoidable) |
| 135 | |
| 136 | ### Verify RED - Watch It Fail |
| 137 | |
| 138 | **MANDATORY. Never skip.** |
| 139 | |
| 140 | ```bash |
| 141 | npm test path/to/test.test.ts |
| 142 | ``` |
| 143 | |
| 144 | Confirm: |
| 145 | - Test fails (not errors) |
| 146 | - Failure message is expected |
| 147 | - Fails because feature missing (not typos) |
| 148 | |
| 149 | **Test passes?** You're testing existing behavior. Fix test. |
| 150 | |
| 151 | **Test |