Test-Driven Development, closely associated with Kent Beck, describes a specific, disciplined cycle: write a failing test for a small piece of desired behavior first (red), write the minimum code needed to make that test pass (green), then improve the code's structure while keeping the test passing (refactor, in the specific, behavior-preserving sense discussed elsewhere on this shelf), and repeat for the next small piece of behavior. The order — test before implementation — is the specific, defining detail that distinguishes TDD from simply having good test coverage. A short external definition is available in the Agile Alliance TDD overview.
Why the order matters, not just the eventual coverage
Writing a test first forces a specific kind of thinking that writing tests afterward doesn't: before any implementation exists, the developer has to articulate precisely what the code is supposed to do, from the perspective of something calling it, rather than describing what the code already does after the fact. Tests written after implementation tend to describe the implementation's actual behavior, including any accidental quirks; tests written first describe the intended behavior, which is a meaningfully different and often more useful thing to have documented and verified. Measurement systems invite gaming when the proxy becomes the target; this page catalogs common examples in time tracking.
A commonly cited secondary effect is that code developed test-first tends to end up more testable by construction — because the code has to be callable and checkable before it exists in order to write a test for it, TDD naturally steers away from designs that are hard to test in isolation (tightly coupled to global state, hard to instantiate without heavy setup), the same properties the legacy-code guide on this shelf identifies as making code hard to safely change later.
What TDD is not a substitute for
TDD is a discipline for writing individual units of code against known, specific requirements — it's not, by itself, a design methodology for an entire system's architecture, and it doesn't replace the higher-level design thinking discussed in the architecture guides on this shelf. A common misapplication is treating strict, granular TDD as sufficient for figuring out a system's overall structure; in practice, most experienced practitioners combine TDD at the level of individual functions and classes with separate, more deliberate architectural thinking at a larger scale, rather than expecting the small-scale red-green-refactor cycle to organically produce good system-level structure on its own.
- The defining detail of TDD is the order — test before implementation — not simply having comprehensive test coverage, which can be achieved either way.
- Writing a test first requires articulating intended behavior precisely, before any implementation exists to describe instead — this is a genuinely different exercise than writing tests against existing code.
- Code developed test-first tends to be more testable by construction, because untestable designs are harder to write a preceding test for in the first place.
- TDD operates well at the scale of individual functions and classes; it isn't a substitute for deliberate, higher-level architectural design.
The honest debate about how strictly to apply it
Strict, disciplined TDD — writing the smallest possible failing test, the smallest possible implementation, repeated in very small increments — has genuine, documented advocates and genuine, documented critics within the field, including experienced practitioners who value automated testing highly but find the strict cycle too rigid for exploratory work where the eventual shape of the solution isn't yet clear. A common middle position treats TDD as one valuable tool, well suited to code with clear, already-understood requirements, and less well suited to early, exploratory design work where the requirements themselves are still being discovered through the act of writing code — in which case tests may reasonably follow rather than precede that exploratory phase.
Whether or not a team adopts the full, strict cycle, the underlying habit — being able to state precisely what a piece of code should do before deciding how it will do it — is a transferable discipline that shows up, in a looser form, in most well-tested codebases regardless of exactly when each individual test was written relative to the code it checks.