This is not a blog post about the usual line on unit tests. They're good. They help you catch problems very early in the development process. High coverage numbers speak well for your codebase. These are all generally true statements and their complexities have been adequately covered elsewhere.
This is also not a blog post about test-driven development (TDD) or any of its related concepts. These are good frameworks for particular types of work which promote robust unit testing. TDD is not universally applicable, however, and indeed is not part of the process I will be describing. I want to talk about TWERP – "Testing Will Enhance Rapid Prototypes."
Rapid prototyping and unit testing - the two goals seem at odds, but they're not. Consider the following:
1. TDD is great when the design can be worked out fully in advance, and all function boundaries are known.
2. Rapid prototyping implies a lack of a priori understanding of the implementation and thus is a bad fit for TDD.
3. After a prototype is functional, writing exhaustive tests provides the development team a chance to look at the implementation with fresh eyes, and refactor many initial mistakes.
It's a pretty simple concept, with one obvious weakness: If the implementation is in a state of iterative evolution, why spend any time writing tests which will also have to be refactored?
There is a cost-benefit analysis here, and it is the crux of the discussion. Let's consider a scale from TDD to Waterfall (writing tests after the code is pronounced complete). If you write tests too far on the TDD side, you must rewrite many of them as you go along, the cost of which is very high. If you write tests too far to the Waterfall side, you will see no refactoring benefits, or the cost of refactoring will be too high to justify. To that end, I propose a middle path.
There is an inflection point where a majority of tests will not have to be thrown away, and the learning gained by writing tests can be acted upon at low cost. We'll call this the TWERP moment. If you hit it right, you will have code that is:
- Better designed.
- Easier to understand.
- More extensible.
- More testable.
- Exhaustively exercised.
This, of course, assumes that you write high-quality tests, and that is a topic for another blog.