Wednesday, August 15, 2012

Code coverage analysis in multi-module Maven projects

When doing refactorings and other maintenance work on mature projects, code coverage analysis is an invaluable tool to help ensuring that the changes have no unexpected side effects. Usually I do that using Cobertura because it's very easy to use in Maven projects (just type mvn cobertura:cobertura and open the report generated under target/site/cobertura). One of the shortcomings of Cobertura is its lack of support for multi-module Maven builds. That is really annoying because in many large projects, a significant amount of code coverage for a given Maven module is actually generated by tests run in other modules. E.g. there are many projects that have a dedicated Maven module for integration tests.

Recently I learnt about JaCoCo which is another code coverage analysis tool. Unfortunately it has the same shortcoming as Cobertura: out of the box, it doesn't support multi-module Maven builds. There is a bug report that contains a patch that solves this issue (at least to some degree; see below). The patch has not been applied yet; in order to use it you will have to build a patched version of JaCoCo manually. The patch can be applied cleanly to revision 1674 of JaCoCo trunk.

Here is the complete set of instructions to build a patched version of JaCoCo and deploy it to the local Maven repository:

svn co -r 1674 https://eclemma.svn.sourceforge.net/svnroot/eclemma/jacoco/trunk/jacoco
cd jacoco
curl http://sourceforge.net/apps/trac/eclemma/raw-attachment/ticket/186/jacoco-maven-aggregate.diff | patch -p0
cd org.jacoco.build
mvn clean install

After that you can use JaCoCo on a multi-module Maven project with the following commands:

mvn org.jacoco:jacoco-maven-plugin:0.5.8-SNAPSHOT:prepare-agent -Daggregate=true clean install
mvn org.jacoco:jacoco-maven-plugin:0.5.8-SNAPSHOT:report -Daggregate=true

There is however one important limitation: because of an issue in code added by the patch, it only works on multi-module projects where the root POM is also the parent POM. If this is not the case, then the plugin will fail with a NullPointerException.