Often when I speak to development teams about their technical debt, one of the issues they highlight is lack of unit test coverage. “We only have 30% coverage, so we’re hoping to set aside some time next sprint to get more tests in place. Our latest work all has 100% coverage, but there’s a lot of code from way-back-when which is completely lacking in tests”.
This seems to me to misunderstand the purpose of unit testing. I can see how this misunderstanding comes about: there is a general acceptance that tests are good, and that a high level of test coverage is good, therefore increasing coverage must be a worthwhile thing. Right?
Well, just stop and think about that for a moment. What is the problem you are solving by adding more tests? “Well, the unit tests will warn us if anything breaks”. If what breaks? You have this collection of classes which you have built, and pieced together they work correctly (I presume) as a suite of software, and… something is going to come and break that. What? A gremlin? A high-speed electron from outer-space? An evil hacker?
It’s conceivable that something might slip into the code-base which unwittingly touches on an existing class, or falls foul of a series of hard-coded dependencies. Possible, but not very likely. What is likely, and does needed to be guarded against, are regressions and errors caused by the developers’ day-to-day coding, wherever this touches upon the existing code base.
Rather than setting aside time for revisiting old code and adding tests, developers should be adding these tests as part of their everyday work, each time they need to do something that touches upon an existing class. If you are adding responsibilities to class X, or changing the way it responds to class Y, or refactoring some of its internal code, then the time to add tests is just before you make those changes. Then these new tests will indeed protect you against breaking functionality – and they will protect you at exactly the time when you are liable to break that functionality. In addition, the mere act of writing the test will force you to become familiar with the target classes (which of course you may not have written yourself), to iron out any lack of clarity, and to determine exactly what outputs correspond to different inputs; in fact, by writing tests you are effectively documenting the existing class.
Of course, there is another time when you should be writing tests: before you write any new code. The first D in TDD is not there as the result of a whim: to get the fullest benefit your development should be driven by your testing. Writing the test first allows you to specify exactly what it is you want the code to do, and this very often provokes new realisations, and better ways of writing the production code.
So, don’t skimp on tests: do them before you write new code; do them before you touch existing untested code. But do not merely add tests to untested code for the sake of “coverage”: if that code is never again modified, then your time spent bumping up your test coverage percentage will have been time wasted.