An open letter about unit tests
An open letter to a programmer who thinks that code coverage by integration tests eliminates the need for unit tests.
Why do we want tests? Typically, we want functional or acceptance test to demonstrate that the system, as a whole, functions the way we (or “the business”) wants it to function. We want integration tests to demonstrate that the subsystems talk to each other properly, as often errors creep in with differing expectations at these major system boundaries. And we want unit or micro tests to demonstrate that the code works the way the programmer thinks it should work. This is the business view of tests.
From a personal point of view, as a programmer I want tests to make my life easier as a programmer. Tests, particularly unit tests, give me quick feedback when my changes to the code have violated previous expectations of the code. They let me know when I’ve accomplished the functionality that lead me to make changes. They let me reorganize the code without worrying about making a mistake, because the tests will immediately alert me if I do. They let me move quickly, because the tests can analyze the holes in my design much more quickly than I can. And they save me from lots of embarrassment that comes from delivering buggy code.
You might think that we only need one level of testing. As long as a line of code is covered by a test, why cover it again? In a perfect world, shouldn’t this be sufficient?
In a perfect world, we wouldn’t need to write tests at all. We’re not in a perfect world, and neither are our tests. Our functional tests can look at the whole system, but has a hard time exercising the edge conditions deep in the code. The fact that a line of code has been exercised by a test does not tell us that the line works properly under various conditions. Our unit tests can easily check the boundaries of a small piece of code, but can’t assure us that the pieces are connected together properly. We need multiple levels of testing to get the assurance we need.
In addition, tests have other attributes. Unit tests run much faster (much less than a second) than integration or functional tests. This lets us run them much more frequently (several times a minute) and quickly get feedback on the effect of our latest code change. They can also diagnose the errors much more precisely than larger scale tests. They can codify our assumptions about the way a piece of code works, so if someone else makes a change that breaks those assumptions it becomes immediately obvious. Done well, unit tests give us confidence to move fast and stay out of the debugger.
If you’re not getting these benefits from unit testing, call me and I’ll work with you on them. Life is too short to struggle with unit tests and not get the value they can offer.