| name | sp-tdd |
| description | Use when writing or modifying any code — enforces the RED-GREEN-REFACTOR cycle: write a failing test, watch it fail, write minimal code, watch it pass, commit. Deletes code written before tests. |
Test-Driven Development (TDD)
Every code change follows RED-GREEN-REFACTOR. No exceptions for "trivial" code; trivial code becomes complex.
The cycle
1. RED Write a failing test that defines the new behavior.
Run it. Watch it fail for the right reason.
2. GREEN Write the minimum code to make the test pass.
Don't add features the test doesn't require.
3. REFACTOR Clean up the code while keeping the test green.
Then commit.
When to use
- Writing a new function, class, module, or API.
- Fixing a bug — write the test that reproduces the bug first.
- Refactoring — the existing tests are your safety net; add tests for behaviour you're preserving.
The Iron Law
No production code without a failing test first.
If you wrote code before writing its test, delete the code. This is not negotiable. Tests written after the code don't catch the bug they were written for; they just confirm what the code already does.
RED phase details
- Test the behavior, not the implementation. The test should fail if the behavior is wrong, not if the implementation changed.
- One test per behavior. If you find yourself writing two tests in the same
it(), split them.
- Watch the test fail. Don't skip this step. A test that passes without the code under test is a tautology; it's not testing anything.
- Right reason to fail. The failure message should point at the missing behavior, not at a syntax error or import problem.
GREEN phase details
- Minimum code to pass. No "while I'm here" additions. No speculative features.
- The simplest passing code is often the dumbest code. That's fine; you can refactor next.
- Don't add tests beyond the one that just failed. The next RED phase adds the next test.
REFACTOR phase details
- Tests stay green throughout. If a refactor breaks a test, revert the refactor.
- Look for: duplication, long functions, deep nesting, magic numbers, naming.
- Don't: add behavior, change the public API, "improve" the test.
Anti-patterns (delete code that does these)
- Tautological tests —
expect(addOne(1)).toBe(addOne(1)). Replace with expect(addOne(1)).toBe(2).
- Test-after-the-fact — code exists, then a "test" is added that just exercises the code. Delete the test; start over.
- Mock-heavy tests — every dependency mocked, the test exercises the mocks, not the code. Reduce mocks; favour the seam.
- Implementation-detail tests —
expect(spy).toHaveBeenCalledWith(...) instead of expect(result).toBe(expected). Refactor to behaviour tests.
- "I'll add tests later" — the tests will never be added. Delete the code.
When TDD doesn't fit
- Spikes / exploratory code — write throwaway code to learn. Throw away the code (and any tests) when done. Start fresh with TDD for the real implementation.
- Generated code — generated by a tool, not authored. Tests go on the public behaviour of the generator's output, not on every line.
- Configuration — JSON / YAML / TOML has no testable behaviour. Use schema validation instead.
Source / license
Adapted from JZKK720/superpowers · test-driven-development, MIT.