Kent Beck in his recent post Functional TDD: A Clash of Cultures summarizes well the key principles and benefits that underlie test-driven development. I think it is really worthwhile becoming aware of and thinking over these foundation stones of TDD (and testing in general). Knowing them enables you to apply TDD in the most effective way with respect to a particular context to gain the maximum of these benefits. People that do not really understand the value of their tests and TDD tend to write hard to maintain tests of limited (and occasionally even negative) value. Are you one of them?
According to Kent, the principles are:
- Double checking. “If I think through a problem two different ways and arrive at the same answer, it’s much more likely the right answer than if I only think one way. I need to keep some form of double checking.” – the production code being one way and the test – simpler and preferably quite different from that code – being the other one.
- Solution decomposition. “I need to be able to work on a part of a problem at a time without having to hold the whole thing in my head at once. Having solved part of a problem I need proof that it is and remains solved.” Failing high-level tests remind you of what yet needs to be implemented and help you focus on those areas. Tests enable you to freely refactor and improve an originally primitive implementation of a component without needing to think of the rest as they set boundaries within which you know you can move without breaking anything else. The tests are automated and thus you always know that that what they assert still holds.
- Automatic verification. “I need the computer to check whether results are correct. Green should mean, as closely as I can approximate, ‘Ready to serve nearly a billion users.'” (Automatic verification also provides a safety net for refactoring and future evolution of the code.)
- Outside in. “The program’s externally visible behavior is more important than its internal structure (leaky abstractions notwithstanding). I want to start my search for a solution from the outside most of the time.” With TDD you focus first on defining the outside-facing API of your classes and only after that you go on evolving the internal implementation.
Kent also mentions, and I must agree, that the continuous cycle of tension (failing test) – relief (green) provides for a good pace and pleasant feeling during development.
There are also other important benefits of testing. My two favourite ones are:
- Documentation. Tests document how the class is to be used and often also why it is as it is, something that is usually not possible to communicate in the code itself.
- Design. TDD doesn’t actually help you to create better designs; instead it makes a bad design painful and thus pushes towards a better one, one that is more cohesive and loosely coupled.
Update: I’ve noticed that I forgot one, for me personally very important aspect of TDD: it is fun. It is not just tension and relief, it is a continuous cycle of rewarding challenges, a cycle of contests between me-tester and me-developer, trying to find out how to test the thing in the simples way and how to fool the test or make it pass in the simples way.
The key message here is that you should know the value of what you are doing and adjust your practice with respect to the context to get the value. Following TDD or testing slavishly without understanding its value is very likely to lead to bad tests that cause more pain then benefit.
(If you like this post then you might also like Principles for Creating Maintainable and Evolvable Tests, Never Mix Public and Private Unit Tests! (Decoupling Tests from Implementation Details) and Clean Test Design. And you might enjoy also other posts on effective development.)