X

Follow us on Facebook

Code Coverage using JUnit, JaCoCo and Sonar

For every code that's written in Java, it's always the right thing to "TEST". As part of testing process, we aim at covering maximum implementation lines possible. In order to measure the test coverage we rely on Code Coverage tools. Among the famous code coverage tools, JaCoCo is famous and is very easy to implement.



Below we will develop a very simple code, write test cases (unit tests and integration tests), attach project to SonarQube server and configure the maven JaCoCo plugin to generate code coverage reports.


We will use maven's quickstart archetype for creating project structure. Below is how it looks like:


Step 1: Simple coding in App.java



package go.eat.learn.springboot.example;


public class App
{
public void sayFruitOrVegetable(String input){

if(input.equalsIgnoreCase("apple")){
System.out.println("It's a fruit");
}else{
System.out.println("It's a vegetable");
}


}
}

Step 2: Unit test code in AppTest.java
 
 package go.eat.learn.springboot.example;

import org.junit.Test;

public class AppTest {

@Test
public void testSayFruitOrVegetable() {
App app = new App();
app.sayFruitOrVegetable("apple");
}
}

Step 3: Integration test coding in AppIT.java

package go.eat.learn.springboot.example;

import org.junit.Test;

public class AppIT {

@Test
public void testSayFruitOrVegetable() {
App app = new App();
app.sayFruitOrVegetable("broccoli");
}

}

Step 4: Set up SonarQube and integrate with eclipse - refer our earlier post
http://www.goeatlearn.com/2015/06/using-sonar-with-eclipse.html

Step 5: Configure pom.xml

Below is entire pom.xml configuration provided for clarity.
Main points to note are:
           a. Configuring sonar properties
           b. Mentioning JaCoCo plugin unit tests end with *Test.java and integration tests end with *IT.java
           c. Configuing m2e plugin to ignore JaCoCo life cycle execution errors (you'll notice :
Plugin execution not covered by lifecycle configuration: org.jacoco:jacoco-maven-plugin. Add exclusions in m2e plugin to ignore it)
           d. Once you configure m2e plugin, still you'll see "x" mark in eclipse's pom - do not worry. Do a maven build by passing  "clean install sonar:sonar" goal - it should be successful

<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>go.eat.learn</groupId>
<artifactId>springboot.example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>springboot.example</name>
<url>http://maven.apache.org</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jacoco.it.execution.data.file>${project.build.directory}/coverage-reports/jacoco-it.exec</jacoco.it.execution.data.file>
<sonar.core.codeCoveragePlugin>jacoco</sonar.core.codeCoveragePlugin>
<sonar.jacoco.reportPath>${project.build.directory}/coverage-reports/jacoco-ut.exec</sonar.jacoco.reportPath>
<sonar.jacoco.itReportPath>${project.build.directory}/coverage-reports/jacoco-it.exec</sonar.jacoco.itReportPath>
<sonar.surefire.reportsPath>${project.build.directory}/surefire-reports</sonar.surefire.reportsPath>
<maven.surefire.plugin.version>2.18.1</maven.surefire.plugin.version>
<maven.failsafe.plugin.version>2.18.1</maven.failsafe.plugin.version>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<!-- Java Code Coverage Start -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.6.4.201312101107</version>
<executions>
<execution>
<id>prepare-unit-test-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
<configuration>

<destFile>${sonar.jacoco.reportPath}</destFile>
<propertyName>surefireArgLine</propertyName>
</configuration>
</execution>
<execution>
<id>generate-unit-test-report</id>
<goals>
<goal>report</goal>
</goals>
<configuration>
<!-- Supplying data file of JaCoCo Unit Tests is must -->
<dataFile>${sonar.jacoco.reportPath}</dataFile>
</configuration>
</execution>
<execution>
<id>prepare-integration-test-agent</id>
<goals>
<goal>prepare-agent-integration</goal>
</goals>
<configuration>
<!-- Same explanation as UT's -->
<destFile>${sonar.jacoco.itReportPath}</destFile>
<propertyName>surefireArgLine</propertyName>
</configuration>
</execution>
<execution>
<id>generate-integration-test-report</id>
<goals>
<goal>report-integration</goal>
</goals>
<configuration>
<!-- Same explanation as UT's -->
<dataFile>${sonar.jacoco.itReportPath}</dataFile>
</configuration>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<!-- Version declaration for surefire is not mandatory at this place
- present for reference purpose -->
<version>${maven.surefire.plugin.version}</version>
<executions>
<execution>
<id>run-unit-tests</id>
<goals>
<goal>test</goal>
</goals>
<configuration>
<argLine>-Xmx1024m -XX:MaxPermSize=256m ${surefireArgLine}</argLine>
<skipTests>${skip.unit.tests}</skipTests><!-- Defaults to false -->
<excludes>
<!-- By default Sonar's JaCoCo expects Integration Tests to be *IT.java
or *ITCase.java -->
<exclude>**/IT*.java</exclude>
</excludes>
</configuration>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${maven.failsafe.plugin.version}</version>
<executions>
<execution>
<id>run-integration-tests</id>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<argLine>-Xmx1024m -XX:MaxPermSize=256m ${surefireArgLine}</argLine>
<skipTests>${skip.integration.tests}</skipTests><!-- Defaults to false -->
<excludes>
<!-- By default Sonar's JaCoCo expects Unit tests to be **Test.java -->
<exclude>**/Test*.java</exclude>
</excludes>
</configuration>
</execution>
</executions>
</plugin>


<!--This plugin's configuration is used to store Eclipse m2e settings
only. It has no influence on the Maven build itself. -->
<plugin>
<groupId>org.eclipse.m2e</groupId>
<artifactId>lifecycle-mapping</artifactId>
<version>1.0.0</version>
<configuration>
<lifecycleMappingMetadata>
<pluginExecutions>
<pluginExecution>
<pluginExecutionFilter>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<versionRange>[0.5,)
</versionRange>
<goals>
<goal>prepare-agent</goal>
</goals>
</pluginExecutionFilter>
<action>
<!-- m2e doesn't know what to do with jacoco, let's ignore it or
annoying error markers appear see http://wiki.eclipse.org/M2E_plugin_execution_not_covered -->
<ignore></ignore>
</action>
</pluginExecution>
</pluginExecutions>
</lifecycleMappingMetadata>
</configuration>
</plugin>
<!-- Java Code Coverage End -->
</plugins>
</build>
</project>



Step 6: Run Maven goal "clean install sonar:sonar"

Step 7: Launch sonar in browser at http://localhost:9000 and log in as admin

Step 8: Select your project on main dashboard



Step 9: Select "More" -> "Manage Dashboards"




Step 10: Select "Integration Tests Coverage" widget as shown below



Step 11: Select "Back to Dashboard"

Step 12: You are done, you'll see Integration Tests coverage and Unit Test coverage as below:


Play around by clicking the coverage percentage, you'll get detailed reports on line coverage for each java file


Share on Google Plus

About Unknown

This is a short description in the author block about the author. You edit it by entering text in the "Biographical Info" field in the user admin panel.
    Blogger Comment
    Facebook Comment

3 comments:

  1. Wonderful. Was looking for something similar - which version of JDK you used?

    ReplyDelete
    Replies
    1. Any JDK higher than 1.5 should do. I've used JDK 6. Thank you for comment :)

      Delete
  2. Thank you..It's encouraging to us :)

    ReplyDelete