Unit tests should tell you more than just if there is an error, they should tell you where it is.
I was going through some of my older notes on unit testing and came across a note where I referred to unit tests as a “Targeting System” and it struck me as a very concrete way to define what it means for unit tests to be “granular” or “fine grained”.
A nuclear bomb is an accurate weapon in that it will destroy its target, but it not very precise since it destroys everything around it for miles. A rifle, on the other hand, is both accurate and precise. It can pick off one target in close proximity to other targets without affecting those other targets.
Unit tests should be more like a rifle than a nuclear bomb. When a test fails, it shouldn’t tell you that your app broke, it should tell you exactly what class and method broke.
The nuclear bomb test
A nuclear test might look like this:
1. Add basic data using business objects
2. Check that a summarized report has correct results.
If there is a failure, you have no idea what caused it. Was it the business object? The database? The report? Something else? Sure, the test has found an error…which is good, but you have no idea where it is…which is bad. In short, this test has no precision. Its exercising way too much code.
Many developers that are new to unit testing write these types of tests, then complain that testing is a waste of time. Their statement is just as imprecise as their tests. Its not testing (in general) that’s a waste of time, its imprecise (or “nuclear”) testing that’s a waste of time.
The rifle test
Rather than writing one huge “test it all at once” nuclear test, it is better to write many small precise tests. These are fine-grained tests that cover each small unit of functionality separately. Here is a sample of possible tests to cover the same code that the single nuclear test covered:
- Test that each business object correctly stores its data in the database.
- Test that each business object correctly loads its data from the database.
- Test that the report loads correct data.
- Test that the report aggregates the data correctly.
This is just a sample, but you can easily see that when one of these tests fail, you have a better idea where to go in the code to find the error.
So we can easily see that our unit tests should be fine-grained. This means that each of them should only cover a small portion of code so that when it fails you don’t have to look far to find the error. Poorly written tests cover a large set of code and thus you have to look through much more code to find the error.
Thinking of unit testing as a “targeting system for errors” will help you to design tests that reveal more than just the fact that an error occurred. They will reveal the source of that error as well. After all, fixing the error usually doesn’t take that much time, but finding it does. Write tests that pinpoint where errors are, and you will spend more time writing code, and less time reading it looking for errors.
-=CE=-