Java Unit Test Code Coverage with SonarQube, Maven and JaCoCo

12:49

I have comfort of inspecting code with Sonar at work. Our configuration manager had installed it some time ago. Recently I wondered unit test coverage of a sample project at home (over my tablet!). I installed SonarQube, it's easy, but I was surprised by the fact that code coverage is not a native feature of it. I integrated JaCoCo Java Code Coverage Library with Maven, and let SonarQube be aware of reports generated by JaCoCo.

It took a bit more than I expected, so I share my experience here. You can also find sample project described in this article on GitHub.

1. Prerequisites

Please ensure that Java and Maven installed and JAVA_HOME is set. Please be aware that SonarQube requires Java 8 onwards
$ java -version
java version "1.8.0_102" ...
$ mvn -version
Apache Maven 3.3.9
$ echo $JAVA_HOME
/path-to-java/jdk1.8.0_92

2. We need a project to work on

Lets create one using maven quickstart archetype. Archetypes are Maven project templates.

$ mvn archetype:generate -DgroupId=net.anatoly.sample.coverage -DartifactId=sample-test-coverage -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

[INFO] Scanning for projects...
Downloading: [SHITLOAD of POMS and JARS]
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:2.4:generate (default-cli) > generate-sources @ standalone-pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:2.4:generate (default-cli) < generate-sources @ standalone-pom <<<
[INFO]
[INFO] --- maven-archetype-plugin:2.4:generate (default-cli) @ standalone-pom ---
Downloading: [SHITLOAD of POMS and JARS]
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Old (1.x) Archetype: maven-archetype-quickstart:1.0
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: basedir, Value: prj\sonar-coverage\initial
[INFO] Parameter: package, Value: net.anatoly.sample.coverage
[INFO] Parameter: groupId, Value: net.anatoly.sample.coverage
[INFO] Parameter: artifactId, Value: sample-test-coverage
[INFO] Parameter: packageName, Value: net.anatoly.sample.coverage
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] project created from Old (1.x) Archetype in dir: prj\sonar-coverage\initial\sample-test-coverage
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 43.636 s
[INFO] Finished at: 2016-12-06T19:37:44+02:00
[INFO] Final Memory: 14M/130M
[INFO] ------------------------------------------------------------------------

Now we have a minimal java maven project with java source and test folders and maven pom file.

Lets test our sample project

$ mvn test
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building sample-test-coverage 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
Downloading: [SHITLOAD of POMS and JARS]
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ sample-test-coverage ---
Downloading: [SHITLOAD of POMS and JARS]
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory prj\sonar-coverage\sonar\sample-test-coverage\src\main\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ sample-test-coverage ---
Downloading: [SHITLOAD of POMS and JARS]
[INFO] Changes detected - recompiling the module!
[WARNING] File encoding has not been set, using platform encoding Cp1252, i.e. build is platform dependent!
[INFO] Compiling 1 source file to prj\sonar-coverage\sonar\sample-test-coverage\target\classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ sample-test-coverage ---
[WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory prj\sonar-coverage\sonar\sample-test-coverage\src\test\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ sample-test-coverage ---
[INFO] Changes detected - recompiling the module!
[WARNING] File encoding has not been set, using platform encoding Cp1252, i.e. build is platform dependent!
[INFO] Compiling 1 source file to prj\sonar-coverage\sonar\sample-test-coverage\target\test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ sample-test-coverage ---
Downloading: [SHITLOAD of POMS and JARS]
[INFO] Surefire report directory: prj\sonar-coverage\sonar\sample-test-coverage\target\surefire-reports
Downloading: [SHITLOAD of POMS and JARS]

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running net.anatoly.sample.coverage.AppTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.006 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 20.200 s
[INFO] Finished at: 2016-12-06T19:58:01+02:00
[INFO] Final Memory: 17M/142M
[INFO] ------------------------------------------------------------------------

Yay! It works.

But it gives encoding warnings, lets set our source encoding to UTF-8. Add below xml snippet to project pom.xml file

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

As in our example:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>net.anatoly.sample.coverage</groupId>
  <artifactId>sample-test-coverage</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>sample-test-coverage</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
</project>

Encoding warnings are gone!

Now we have a sample project.

3. Install SonarQube

Installing SonarQube is easy. Just download, uncompress and run. You can follow the instructions on Sonar - Get Started in Two Minutes.

You can test the server on http://localhost:9000 and initial admin credentials are admin / admin

4. Integrate Maven with Sonar

Linked article above shows analyzing source code by console scanner. Lets integrate it with maven.

Edit the settings.xml file, located in $MAVEN_HOME/conf or ~/.m2, to set the plugin prefix and optionally the SonarQube server URL.

<settings>
    <pluginGroups>
        <pluginGroup>org.sonarsource.scanner.maven</pluginGroup>
    </pluginGroups>
    <profiles>
        <profile>
        <id>sonar</id>
        <activation>
            <activeByDefault>true</activeByDefault>
        </activation>
        <properties>
        <!-- Optional URL to server. Default value is http://localhost:9000 -->
        <sonar.host.url>
            http://localhost:9000
        </sonar.host.url>
        </properties>
        </profile>
    </profiles>
</settings>

And update project pom file properties as

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <sonar.language>java</sonar.language>
</properties> 

Now lets check if it works:

$ mvn sonar:sonar
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building sample-test-coverage 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- sonar-maven-plugin:3.2:sonar (default-cli) @ sample-test-coverage ---
[unneeded details for this article]
[INFO] Analysis report generated in 134ms, dir size=19 KB
[INFO] Analysis reports compressed in 25ms, zip size=9 KB
[INFO] Analysis report uploaded in 31ms
[INFO] ANALYSIS SUCCESSFUL, you can browse http://localhost:9000/dashboard/index/net.anatoly.sample.coverage:sample-test-coverage
[INFO] Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report
[INFO] More about the report processing at http://localhost:9000/api/ce/task?id=AVjVgWSwUIL7exiiY2o8
[INFO] Task total time: 5.402 s
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 10.147 s
[INFO] Finished at: 2016-12-06T20:59:36+02:00
[INFO] Final Memory: 24M/227M
[INFO] ------------------------------------------------------------------------

Generated report can be seen on http://localhost:9000

5 Integrate JaCoCo with Maven

Update pom.xml with below xml snippets

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <sonar.language>java</sonar.language>
    <sonar.jacoco.reportPath>target/coverage-reports/jacoco-ut.exec</sonar.jacoco.reportPath>
</properties>
 
<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-surefire-plugin</artifactId>
      <version>2.15</version>
      <configuration>
      <!-- Sets the VM argument line used when unit tests are run. -->
      <argLine>${surefireArgLine}</argLine>
      <!-- Skips unit tests if the value of skip.unit.tests property is true -->
      <skipTests>${skip.unit.tests}</skipTests>
      <!-- Excludes integration tests when unit tests are run. -->
      <excludes>
        <exclude>**/IT*.java</exclude>
      </excludes>
      </configuration>
    </plugin>
    <plugin>
      <groupId>org.jacoco</groupId>
      <artifactId>jacoco-maven-plugin</artifactId>
      <version>0.7.7.201606060606</version>
      <executions>
        <!--
        Prepares the property pointing to the JaCoCo runtime agent which
        is passed as VM argument when Maven the Surefire plugin is executed.
        -->
        <execution>
          <id>pre-unit-test</id>
          <goals>
            <goal>prepare-agent</goal>
          </goals>
          <configuration>
            <!-- Sets the path to the file which contains the execution data. -->
            <destFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</destFile>
            <!--
            Sets the name of the property containing the settings
            for JaCoCo runtime agent.
            -->
            <propertyName>surefireArgLine</propertyName>
          </configuration>
        </execution>
        <!-- 
        Ensures that the code coverage report for unit tests is created after
        unit tests have been run.
        -->
        <execution>
          <id>post-unit-test</id>
          <phase>test</phase>
          <goals>
            <goal>report</goal>
          </goals>
          <configuration>
            <!-- Sets the path to the file which contains the execution data. -->
            <dataFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</dataFile>
            <!-- Sets the output directory for the code coverage report. -->
            <outputDirectory>${project.reporting.outputDirectory}/jacoco-ut</outputDirectory>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

Thanks to Petri Kainulainen for this part.

Now when we test our project, code coverage reports will also be generated. Nice!

$ mvn clean test
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building sample-test-coverage 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ sample-test-coverage ---
[INFO] Deleting prj\sonar-coverage\jacoco\sample-test-coverage\target
[INFO]
[INFO] --- jacoco-maven-plugin:0.7.7.201606060606:prepare-agent (pre-unit-test) @ sample-test-coverage ---
[INFO] surefireArgLine set to "-javaagent:\\.m2\\repository\\org\\jacoco\\org.jacoco.agent\\0.7.7.201606060606\\org.jacoco.agent-0.7.7.201606060606-runtime.jar=destfile=prj\\sonar-coverage\\jacoco\\sample-test-coverage\\target\\coverage-reports\\jacoco-ut.exec"
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ sample-test-coverage ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory prj\sonar-coverage\jacoco\sample-test-coverage\src\main\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ sample-test-coverage ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to prj\sonar-coverage\jacoco\sample-test-coverage\target\classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ sample-test-coverage ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory prj\sonar-coverage\jacoco\sample-test-coverage\src\test\resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ sample-test-coverage ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to prj\sonar-coverage\jacoco\sample-test-coverage\target\test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.15:test (default-test) @ sample-test-coverage ---
[INFO] Surefire report directory: prj\sonar-coverage\jacoco\sample-test-coverage\target\surefire-reports

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running net.anatoly.sample.coverage.AppTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.03 sec - in net.anatoly.sample.coverage.AppTest

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

[INFO]
[INFO] --- jacoco-maven-plugin:0.7.7.201606060606:report (post-unit-test) @ sample-test-coverage ---
[INFO] Loading execution data file prj\sonar-coverage\jacoco\sample-test-coverage\target\coverage-reports\jacoco-ut.exec
[INFO] Analyzed bundle 'sample-test-coverage' with 1 classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 4.512 s
[INFO] Finished at: 2016-12-06T21:46:41+02:00
[INFO] Final Memory: 18M/140M
[INFO] ------------------------------------------------------------------------

6. Mission Complete

 $ mvn sonar:sonar

And lets check on the server now!

Finally we have code coverage section on our report page. In case you missed any step you can check initial and final project files on GitHub. Please note that -Step 4- requires editing maven settings.xml which is outside the project folder.

7. Its now time to write actual code and tests ;)

You Might Also Like

0 yorum