Chapter 78. Test-Driven Development
Test-driven development (TDD) is widely misunderstood. Before TDD, the only thing that applied pressure for high quality in software was the knowledge, experience, and commitment of a programmer. After TDD, there was something else.
Good separation of concerns
Testable code has those properties. TDD is development (design) driven by tests. In TDD, we write the test before writing code that makes the test pass. TDD is much more than “good unit testing.”
Writing the test first is important; it means that we always end up with “testable” code. It also means that coverage is never an issue. If we write the test first, we always have great coverage and don’t need to worry about it as a metric—and it is a poor metric.
TDD amplifies the talent of a software developer. It doesn’t make bad programmers great, but it makes any programmer better.
We write a test and see it fail (Red).
We write the minimum code to make it pass and see it pass (Green).
We refactor the code, and the test, to make them as clean, expressive, elegant, and simple as we can (Refactor).
These steps represent three distinct phases in the design of our code. We should be thinking differently during each of these steps.
Focus on expressing the behavioral intent of your code. Concentrate only on the public interface of your code. That is all that we are designing at this point—nothing else.
Think only about how to write a nice, clear test that captures just what you would like your code to do.
Focus on the design of the public interface by making the test simple to write. If your ideas are easy to express in your test, they will also be easy to express when someone uses your code.
Do the simplest thing that makes the test pass. Even if that simple thing seems naive. As long as the test is failing, your code is broken, and you are at an unstable point in the development. Get back to safety (Green) as quickly and simply as you can.
Your tests should grow to form a “behavioral specification” for your code. Adopting the discipline of writing code only when you have a failing test helps to better elaborate and evolve that specification.
Once back to Green, you can safely refactor. This keeps you honest and stops you from wandering off into the weeds and getting lost! Make small simple steps, and then rerun the tests to confirm that everything still works.
Refactoring is not an afterthought. This is an opportunity to think more strategically about your design. If the setup of your tests is too complex, your code probably has poor separation of concerns and may be too tightly coupled to other things. If you need to include too many other classes to test your code, perhaps your code is not very cohesive.
Practice a pause for refactoring every time you achieve a passing test. Always look and reflect, “Could I do this better?” The three phases of TDD are distinct, and your mental focus should also be distinct to maximize the benefit of each phase.