Write the test first, let it drive design, and build confidence with every keystroke.
Test-Driven Development is simple to describe and hard to master:
That's it. Three steps, repeated hundreds of times a day.
The order matters. You write the test first, before any production code exists. This feels backwards at first. But it changes everything:
TDD Is Design
TDD is a design technique disguised as a testing technique. The tests aren't the point—the thinking that produces them is.
The hardest part of TDD is learning to write tests for code that doesn't exist yet. Here's how:
Start with the interface, not the implementation. What should the code look like when it's done? Write a test that uses it that way.
Start small. Don't try to test everything at once. Test the simplest possible case first.
Make it fail for the right reason. The test should fail because the functionality doesn't exist, not because you made a syntax error.
Use test names as documentation. The test name should describe what behavior you're testing: "returns empty list when no items match filter" not "test1."
Writing the test first forces you to think about:
These are design questions. By answering them before you write the code, you make better design decisions.
Need a function to calculate shipping cost. Write a test: 'shipping to California with $50 order is $5.' Then write just enough code to make it pass. Then test the next case.
Write the entire shipping calculation with all edge cases. Then try to write tests. Discover the code is hard to test because it wasn't designed for testability. Write messy tests or skip testing.
Once you have a failing test, make it pass. But here's the discipline: write the minimum code to pass the test, no more.
This means:
This sounds ridiculous. "Just return 5?" Yes, if that makes the test pass. Then write another test that forces you to do real computation.
Why? Because:
The magic happens when you triangulate. First test: return 5. Second test (different input): now you have to compute. Third test: now you have to handle edge cases. Each test pushes you toward a real implementation.
If you can make the test pass by writing 'return 5', write 'return 5'. Then write another test that breaks it. Let the tests drive you toward real code.
After the test passes, clean up. This is not optional.
The refactoring step is where you:
The key: keep the tests passing throughout. Refactor in tiny steps, running tests after each change. If tests fail, you know exactly what broke.
This is where TDD pays off. Without tests, refactoring is terrifying—you might break something. With tests, refactoring is routine—you'll know immediately if you break something.
Skipping the refactoring step leads to what Kent Beck calls "quick green excuses." The code works, but it's messy. The mess accumulates. Soon the codebase is hard to work with, and you've lost the benefit of TDD.
Myth: TDD is slower. Reality: TDD seems slower at first because you're writing more code. But you spend less time debugging, less time in the debugger, less time fixing production bugs. Over the life of the codebase, TDD is faster.
Myth: TDD leads to over-testing. Reality: TDD leads to exactly the tests you need—no more, no less. You test what you build. Test-after often leads to missing tests (you forget edge cases) or redundant tests (you test the same thing multiple ways).
Myth: TDD doesn't work for [my domain]. Reality: TDD works for any code that can be tested. If your code can't be tested, that's a design problem. TDD forces you to write testable code, which is better code.
Myth: We don't have time for TDD. Reality: You don't have time not to. Bugs are expensive. Debugging is expensive. Production incidents are expensive. TDD is an investment that pays off.
Non-Negotiable
In XP, TDD isn't a nice-to-have. It's a non-negotiable practice. Without tests, every other practice becomes harder or impossible.