The Pursuit of Coverage...

I've never really got on fully with unit testing for various reasons. Most developers I've worked with know how to write a test but don't seem to know what to test, locking in behaviours that need not be fixed, are extraneous or are undesirable. I'm by no means an exception to this!

The pursuit of coverage seems to lead to smaller and smaller test cases: TestEveryMethod feels like an anti-pattern to me. The definition of a unit as being the smallest "testable" part of an application is very weak. After all, we choose what to make testable as part of our design process. Are units defined by the design or the design by the resolution of units desired? Testing is simply one of the forces on our design and needs to be weighed in the balance like everything else. The benefits can be vast, but the drawbacks can also be significant.

Coverage reports have always made me slightly suspicious. Their usage is usually tempered with the caveat to "focus on the more critical sections of code: business logic etc." From a process standpoint it would seem prudent to get the coverage report to do the focusing automatically. Coverage also tends to be self-fulfilling; I certainly can't resist the temptation to push up line or branch coverage in a spare moment. The naive approach to doing this is to write a test case, thereby succumbing to the force of the testing on the design without any consideration for the other forces.

I've recently found that a coverage report from a functional test points you in another direction: to remove the untested code. For example, a typical JavaBean contains as much code for equals, hashCode and toString as setters and getters. Surprisingly few domain objects have equality or hash characteristics that matter to anything other than unit tests (ie, for verifying return values). A functional test won't use it, but removing it might break a unit test which relies on it; is this investment in code with no business value paid off by the benefits of testing? Obviously the answer depends on the context, but knowing that you have a method (or behaviour) that exists solely or largely to support testing rather than the requirements changes the forces on the design. Introspection-based implementations or comparators that form part of your test support classes rather than polluting your domain model are possibilities. Furthermore, the notion of equality (and hence hash values too) is context-specific; a unit test probably wants to check every property whereas in the context of the application comparing a unique identifier might be sufficient. Has the unit test polluted the design?

Comments