Skills on AI 459 skills

Active theme: Light

Test Driven Development

Use when asked to write code test-driven — red/green/refactor, writing the failing test first, or explaining TDD's benefits and limits — as distinct from behavior-driven development's given/when/then framing (see behavior-driven-development) or writing tests after the fact.

Test-Driven Development (TDD) is a discipline where a failing test is written before the implementation code that makes it pass — the test drives the design, rather than being retrofitted to verify code already written.

The red/green/refactor cycle

  1. Red — write a small test for behavior that doesn't exist yet; run it and watch it fail. This step is not optional: a test you've never seen fail hasn't proven it can actually catch the bug it claims to catch.
  2. Green — write the minimum code needed to make the test pass, even if it's not the final design (a literal hard-coded return value is a legitimate first green step for a first test).
  3. Refactor — clean up the implementation (and the test, if needed) now that a passing test protects against regressions, without changing observable behavior.

Repeat in small steps — a single cycle is usually minutes, not hours. Skipping straight to a large implementation before writing any test isn't TDD; neither is writing all the tests up front and then all the implementation.

What TDD actually drives

  • Design pressure toward testable units. Code that's hard to test (tight coupling, hidden dependencies, global state) resists TDD's red-first step — the friction is real feedback about the design, not an obstacle to work around.
  • A regression suite as a byproduct, not the primary goal — the tests written along the way accumulate into the safety net that makes later refactoring safe.
  • Confidence to refactor aggressively. Because every behavior change is covered by a test that existed before the code, a developer can restructure implementation freely and trust the suite to catch a regression.

What TDD is not good at

  • It doesn't replace requirements clarity. TDD verifies that code does what the test says — if the test encodes the wrong requirement, TDD cycles through red/green/refactor while building the wrong thing efficiently. See Behavior Driven Development for a framing aimed at closing that specific gap.
  • It doesn't automatically produce good architecture at the system level — TDD's feedback operates at the unit/class level; higher-level design still needs deliberate thought.
  • Exploratory/spike work (figuring out if an approach is even feasible) is often faster done without strict TDD, then followed by a TDD rewrite of whatever the spike proved viable.

Common pitfalls

  • Writing the test after the implementation and calling it TDD — the red step is what proves the test can fail; skipping it means the test might be vacuously passing regardless of the code.
  • Testing implementation details instead of behavior — a test coupled to internal structure breaks on every refactor, defeating the refactor-safety TDD is supposed to provide. Test through the public interface/observable behavior.
  • Writing a large test for a not-yet-existing large feature, then struggling to make it pass in one leap — defeats the small-steps discipline; break it into the smallest test that can meaningfully fail first.
  • Skipping the refactor step once green — tests passing is not the end state; cleanup while the tests still protect you is the point where a lot of TDD's value is actually realized.
  • Treating 100% coverage as the goal — coverage is a byproduct of testing behavior thoroughly, not a target to game by writing low-value tests against trivial code paths.

Learn more

  • Kent Beck, Test-Driven Development: By Example — the originating text.
  • Behavior Driven Development for the given/when/then framing that layers on top of TDD's discipline to keep tests aligned with actual requirements.
  • Specification Driven Development for writing the specification before either the test or the code.

View test-driven-development/SKILL.md on GitHub