Skip to content

Test Driven Development

What Is TDD?

Test Driven Development (TDD) is a software development approach where you write a failing test before you write the code that makes it pass. The goal isn't just to produce tests — it's to use tests as a tool for thinking through your design and requirements before committing to an implementation.

Kent Beck, who popularized TDD through Extreme Programming (XP) in the late 1990s, described the goal simply: write clean code that works.

There are two ways to think about TDD:

  • As a specification tool — writing a test forces you to define exactly what your code should do before you write it.
  • As a programming technique — it keeps your code focused, minimal, and verifiable at every step.

Both views have merit, and in practice they work together.


The TDD Cycle: Red → Green → Refactor

TDD follows a short, repeating loop of three phases:

Red → Green → Refactor → Repeat

🔴 Red — Write a failing test

Write a test for a small piece of functionality that doesn't exist yet. The test must fail — if it passes immediately, either the feature already exists or the test is wrong. This failing test is your starting point and your specification.

🟢 Green — Write just enough code to pass

Write the minimum amount of production code needed to make the test pass. Don't over-engineer. Even a hardcoded value is acceptable at this stage — the goal is simply to go from red to green.

🔵 Refactor — Clean up

Now that the test passes, improve the code: remove duplication, improve naming, simplify structure. Run the tests again to confirm nothing broke. The test suite is your safety net during refactoring.

Then repeat — write the next failing test.


Why Write the Test First?

This can seem backwards at first. Why not write the code and then verify it with tests? Here's why test-first matters:

It forces clarity before commitment. You can't write a test without knowing what the code should do. Writing tests first requires you to think through requirements, edge cases, and interfaces before touching production code.

It keeps code focused and minimal. With TDD, you only write code that makes a test pass. This naturally discourages over-engineering and unused features — a principle sometimes called YAGNI (You Aren't Gonna Need It).

It builds a regression safety net automatically. Every feature you build leaves behind a test that verifies it still works. This makes future changes much safer.

Tests become living documentation. Well-written unit tests show exactly how a class or method is intended to be used. Other developers (and future you) can read the tests to understand the code's expected behavior without digging through implementation details.


TDD and Code Quality

TDD tends to push code toward better design — not as a rule, but as a natural consequence. When you write tests first:

  • Code becomes testable by design. Tightly coupled, hard-to-isolate code is difficult to test, so TDD naturally encourages looser coupling.
  • Units stay small and focused. Small, cohesive methods and classes are easier to test in isolation.
  • Defects surface earlier. Catching a bug after writing two lines of code is far easier than tracking it down in two thousand.

As software engineer Robert C. Martin puts it:

"The act of writing a unit test is more an act of design than of verification. It is also more an act of documentation than of verification."


TDD Is Not a Silver Bullet

TDD is a powerful technique, but it doesn't replace all other forms of testing. A few honest realities:

  • Unit tests alone don't test everything. User interfaces, database interactions, distributed systems, and integration between components require additional testing strategies beyond unit-level TDD.
  • A passing test suite isn't a guarantee of correctness. If you misunderstand a requirement, you can write a test that encodes the wrong assumption — and it will pass every time.
  • Tests have maintenance costs. Tests tightly coupled to implementation details become brittle and expensive to maintain as code evolves.

TDD is most effective as one part of a broader quality strategy, not the whole picture.


TDD in This Course

In this course you'll encounter TDD through running tests we wrote for you. When you open a lab project and the tests, you are running a suite of tests written before you wrote any implementation code. Your job is to write code that makes those tests pass; which is the Green phase of TDD in action.

You won't be writing the tests yourself yet, but understanding why they were written first, and what each test is specifying, is an important professional skill. As you read through a test file, ask yourself: what behavior is this test defining? What would make it fail? What would make it pass?

Connecting TDD to AI-Assisted Development

Modern AI coding tools (like GitHub Copilot, Cursor, or Claude) can generate code quickly, but they can also generate wrong code confidently. TDD is an important counterbalance: a clear set of failing tests gives you an objective, automated way to verify whether AI-generated code actually does what you need. Developers who understand TDD are better equipped to use AI tools safely and effectively.


Two Levels of TDD

Level Also Called Goal
Developer TDD Unit TDD Specify and verify small units of code (methods, classes)
Acceptance TDD (ATDD) Behavior Driven Development (BDD) Specify and verify that the system meets business requirements

In this course you'll primarily encounter Developer TDD. Acceptance TDD is more common in professional agile team settings where customers, developers, and testers collaborate on defining test criteria.


Common Misconceptions

"TDD means 100% test coverage."
Not exactly. TDD results in high coverage as a by-product, but chasing 100% coverage can lead to low-value tests. The goal is meaningful tests, not a metric.

"TDD replaces design and documentation."
Tests capture what code does, but don't capture higher-level architecture, user experience, or business context. TDD works best alongside other design practices.

"TDD slows you down."
In the short term, writing tests first does take more time per feature. In the medium and long term, the reduction in debugging, rework, and regression defects tends to more than compensate, especially on larger codebases.


Further Reading