Friday, March 29, 2013

Measuring overall code coverage in multi-module Maven project

As we know unfortunately Maven has no out of the box support for different test types. There are in fact few open tickets for adapting Maven to different test strategies, like adding integrationTestSourceDirectory to POM model (MNG-2009) or new lifecycle phases for operating on integration test sources (MNG-2010) to replace necessity of using build-helper-maven-plugin.

But apart from separating varying tests from each other how can we use available mechanisms to invoke both unit and integration tests and measure theirs code coverage? I want to achieve such situation (maybe except the number of tests because I usually want to have a little bit more :)) on the Sonar dashboard:

We'll work on a simple maven project with the popular layout:

Both modules contains two "production" classes: one to be tested using unit tests and other for integration testing. Structure of the project looks like below (I hope class names are pretty obvious):


The first thing we have to do is to enable proper widget in Sonar. To do it just click on the dashboard's "Configure widgets", select "Integration tests coverage" item and place it in the appropriate position.

Now it's time to adjust our project to handle suitable coverage measurement tool. I prefer JaCoCo because it is well integrated with Sonar and can be adapted to work with different test types. As a test framework we'll use TestNG. So let's add to our master pom.xml following dependencies:
 
  <dependencies>
    <dependency>
      <groupid>org.testng</groupid>
      <artifactid>testng</artifactid>
      <version>6.7</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupid>org.jacoco</groupid>
      <artifactid>org.jacoco.core</artifactid>
      <version>0.6.2.201302030002</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
In TestNG we can combine tests in groups by using @Test(group="groupName"), and we're going to use this functionality and use "unit" group for all unit tests and "int" group for integration ones.

We must also set sonar properties:
  <properties>
    <!-- select JaCoCo as a coverage tool -->
    <sonar.core.codeCoveragePlugin>jacoco</sonar.core.codeCoveragePlugin>
    <!-- force sonar to reuse reports generated during build cycle -->
    <sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis>
    <!-- set path for unit tests reports -->
    <sonar.jacoco.reportPath>${project.basedir}/target/jacoco-unit.exec</sonar.jacoco.reportPath>
    <!-- all modules have to use the same integration tests report file -->
    <sonar.jacoco.itReportPath>${project.basedir}/../target/jacoco-it.exec</sonar.jacoco.itReportPath>
  </properties>
The next thing we've to do is to configure Maven plugins. To the standard surefire configuration we'll add exclusion of the "int" tests group:
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.14</version>
        <configuration>
          <excludedGroups>int</excludedGroups>
        </configuration>
      </plugin>
Now a little bit more complex situation with configuring failsafe plugin. Failsafe is more suitable for integration tests because when a test fails, it does not immediately abort (instead it lets the clean-up by processing post-integration-test phase).
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-failsafe-plugin</artifactId>
        <version>2.14</version>
        <configuration>
          <!-- property set by jacoco-maven-plugin -->
          <argLine>${itCoverageAgent}</argLine>
          <groups>int</groups>
          <!-- by default only IT*, *IT and *ITCase classes are included -->
          <includes>
            <include>**/*.java</include>
          </includes>
        </configuration>
        <executions>
          <execution>
            <id>integration-test</id>
            <goals>
              <goal>integration-test</goal>
              <goal>verify</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
The last step is adding jacoco plugin:
      <plugin>
        <groupId>org.jacoco</groupId>
        <artifactId>jacoco-maven-plugin</artifactId>
        <version>0.6.2.201302030002</version>
        <executions>
          <!-- prepare agent for measuring unit tests -->
          <execution>
            <id>prepare-unit-tests</id>
            <goals>
              <goal>prepare-agent</goal>
            </goals>
            <configuration>
              <destFile>${sonar.jacoco.reportPath}</destFile>
            </configuration>
          </execution>

          <!-- prepare agent for measuring integration tests -->
          <execution>
            <id>prepare-integration-tests</id>
            <goals>
              <goal>prepare-agent</goal>
            </goals>
            <phase>pre-integration-test</phase>
            <configuration>
              <destFile>${sonar.jacoco.itReportPath}</destFile>
              <propertyName>itCoverageAgent</propertyName>
            </configuration>
          </execution>
        </executions>
      </plugin>
Now invoke normal maven build (by typing mvn clean install). We can see in the compilation output that JaCoCo plugin is invoked both for unit
[INFO] --- jacoco-maven-plugin:0.6.2.201302030002:prepare-agent (prepare-unit-tests) @ first ---
and integration tests
[INFO] --- jacoco-maven-plugin:0.6.2.201302030002:prepare-agent (prepare-integration-tests) @ first ---
While build finished successful we can proceed to running Sonar: mvn sonar:sonar

I hope everything went ok, and now you can admire well measured code coverage on your Sonar dashboard!

1 comment:

Hari Babu said...

Excellent blog! Indeed saved lot of time and effort when configuring the code coverage with Sonar