How to Start Testing A Large Legacy Application
This session morphed into a discussion right up the other end of the spectrum, about achieving 100% test coverage, what that means exactly and whether attempting such a thing is even wise.
What does 100% test coverage actually mean?
- Type of coverage - line, branch or path.
- Tests being run - unit, integration, functional or some combination of these.
- Code being covered - all production code, perhaps with some acceptable exclusions.
We discussed one greenfields Java project, which achieved 100% branch coverage of all production code except for a thin wrapping layer around external third party libraries, running only fairly tight unit tests. Integration and functional tests were not used for coverage measurements. This was done in an attempt to enforce test driving of production code, since it would be nearly impossible to achieve this result without having done so. It succeeded in this respect, but at the cost of having a reasonably large amount of fairly brittle test code to maintain, due to the fairly tight coupling between the tests and implementation details within the production classes.
Achieving this level of coverage was made more difficult when the code needed to call through to external library code which was not designed for testability. This includes the vast majority of all third party libraries and the JDK in particular! Language constructs which make achieving full test coverage more difficult include:
- Referencing concrete classes instead of interfaces.
- Direct use of constructors, rather than factories.
- Final classes.
- Static methods.
Generally, use of any construct which makes it harder to replace real dependencies with test versions needs to be avoided.