Should you strive for 100% code coverage from your unit tests? It’s probably not mandatory and if your goal is 100% coverage, you’ll focus on that goal and not focus on writing the best tests for the behavior of your code.
That said, here are some thoughts on why I still like to get close to 100%.
- I’m anal retentive. I don’t like those little red bits in my coverage report. (Okay, that’s not a good reason…)
- Every time I run the coverage report, I have to inspect all the uninteresting cases to find the interesting cases I should cover.
- The tests are the specification and documentation of the code, so if something nontrivial but unexpected happens, there should still be a test to “document” the behavior, even if the test is hard to write.
- Maybe those places without coverage are telling me to fix the design.
I was thinking about this last point the other day when considering a bit of Java code that does a downcast (assume that’s a good idea, for the sake of argument…), wrapped in a try/catch block for the potential ClassCastException
:
public void handleEvent (Event event) throws ApplicationException { try { SpecialEvent specialEvent = (SpecialEvent) event; doSomethingSpecial (specialEvent); } catch (ClassCastException cce) { throw new ApplicationException(cce); } }
To get 100% coverage, you would have to write a test that inputs an object of a different subtype of Event
to trigger coverage of the catch block. As we all know, these sorts of error-handling code blocks are typically the hardest to cover and ones we’re most likely to ignore. (When was the last time you saw a ClassCastException
anyway?)
So my thought was this, we want 100% of the production code to be developed with TDD, so what if we made 100% coverage a similar goal? How would that change our designs? We might decide that since we have to write a test to cover this error-handling scenario, maybe we should rethink the scenario itself. Is it necessary? Could we eliminate the catch block with a better overall design, in this case, making sure that we test all callers and ensure that they obey the method’s ‘contract’? Should we just let the ClassCastException
fly out of the function and let a higher-level catch block handle it? After all, catching and rethrowing a different exception is slightly smelly and the code would be cleaner without the try/catch block. (For completeness, a good use of exception wrapping is to avoid namespace pollution. We might not want application layer A to know anything about layer C’s exception types, so we wrap a C exception in an A exception, which gets passed through layer B…)
100% coverage is often impossible or impractical, because of language or tool oddities. Still, if you give in early, you’re overlooking some potential benefits.