Test-Driven Development
02 / 02

TDD as a Design Tool, Tradeoffs & Limits

TDD as a Design Tool, Tradeoffs & Limits

Design Tool, Not Just Testing Tool

Writing the test first forces thinking about HOW code will be called (interface, inputs, outputs) before implementation details absorb attention. An awkward-to-test interface tends to reveal itself as awkward specifically because writing a clean test for it is hard — tests as a byproduct, better design as the real driver.

Test Behavior, Not Implementation

A test coupled to internal implementation details (not just observable behavior) breaks on refactoring even when external behavior hasn't changed — turning the test into an obstacle instead of an enabler. Writing tests against behavior, not internals, is what actually makes Refactor safe.

When TDD Is Harder to Apply

Exploratory coding, visually-judged UI work, or experimenting with an unfamiliar API can make writing a meaningful test upfront difficult. A "spike" — quick, disposable exploratory code with no TDD discipline — followed by a proper TDD reimplementation once the approach is understood, is a common pragmatic compromise.

Legacy Code Bootstrapping

Untested legacy code is often ALSO hard to add tests to (tight coupling, global state) — adopting TDD going forward frequently requires first investing in making existing code more testable before new TDD-driven work proceeds smoothly.

BDD & Proportional Rigor

Behavior-Driven Development extends TDD's "specify before implementing" idea into business-readable Given/When/Then specs (Cucumber-style), making the spec accessible to non-technical stakeholders. Applying TDD's discipline proportionally — heavier for complex business logic, lighter for trivial pass-through code — is a reasonable middle ground between strict universal adoption and skipping it entirely.

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free