Jenkins, JaCoCo, and SonarQube Integration With Maven

Using Jenkins to build your application, running tests with Jacoco code coverage, making SonarQube analysis, and saving all results to SonarQube online is a great way of deploying your applications. 

In this article you will be able to use Jenkins with Maven and manage following tasks:

Other common tasks like pulling source codes from Version Control System or deploying the war to the Application Server is out of scope of this article.

Image title

Jenkins

Jenkins is an award-winning, cross-platform, continuous integration and continuous delivery application. Use Jenkins to build and test your software projects continuously making it easier for developers to integrate changes to the project, and making it easier for users to obtain a fresh build. It also allows you to continuously deliver your software by providing powerful ways to define your build pipelines and integrating with a large number of testing and deployment technologies.

First you need a Jenkins instance. If you already have one you can use it. You can download the latest release from https://jenkins-ci.org/

Jenkins Sonar Plugin

Install Jenkins "SonarQube Plugin" with Update Center (not Sonargraph). Plugin url: 

https://wiki.jenkins-ci.org/display/JENKINS/SonarQube+plugin 


SonarQube

SonarQube is an open platform to manage code quality. As such, it covers the 7 axes:  Duplicated code, Coding standards, Unit tests, Complex code, Potential bugs, Comments, Design and architecture

If you don't have a SonarQube instance you need to install one from http://www.sonarqube.org/. 

SonarQube server configuration In Jenkins

To add a new SonarQube server follow "Manage Jenkins -> Configure System" and find “Add Sonar” option. Under the Advanced link fill in the server properties:

Name: MySonarServer
Server URL: http://10.210.99.88:9000
Sonar account login: sonar
Sonar account password: myPass
Database URL: jdbc:mysql://10.210.99.88:3306/sonar?useUnicode=true&characterEncoding=utf8
Database login: sonar
Database password: myDbPass
Database driver: com.mysql.jdbc.Driver


Coverage Option for Build Script 

Add "verify -Prun-its,coverage" maven build option to catch test coverage report. Example:


Failed Test Behavior

Maven has a parameter to stop or continue after failed tests. In ideal case if the tests fail, it is better to stop builds. In Jenkins, you can set this option with "Build-> Advanced-> MAVEN_OPTS" and append the following parameter.

-Dmaven.test.failure.ignore=true

Adding SonarQube to Jenkins Post-Build

You can add Sonar analysis to Jenkins with “Add Post-build Actions” option. This is a list, so if there is more than one Sonar installation choose the one you added.

POM Updates

Inside the <properties> tag add SonarQube project properties. If you do not set any property SonarQube will use the default value.

<!--  This format is used by SonarQube. If you need another format see "buildnumber-maven-plugin" -->
<maven.build.timestamp.format>MM.yyyy</maven.build.timestamp.format>
<yearMonth>${maven.build.timestamp}</yearMonth>    
<!-- ************************-->
<!-- Sonar/Reporting settings -->
<!-- ************************-->
<!-- Sonar/Jacoco integration. Note that these properties need to be defined outside the "coverage" profile
because we want to be to able to execute mvn sonar:sonar without passing a profile -->
<!-- Tells Sonar to use jacoco for coverage results -->
<sonar.projectKey>MyProjectKey</sonar.projectKey>
<sonar.projectName>My Project</sonar.projectName>
 <sonar.projectVersion>${yearMonth}</sonar.projectVersion>
<sonar.language>java</sonar.language>
<sonar.sourceEncoding>UTF-8</sonar.sourceEncoding>
<sonar.java.coveragePlugin>jacoco</sonar.java.coveragePlugin>
<!-- Jacoco version to use -->
<jacoco.version>0.7.2.201409121644</jacoco.version>
<!-- The Sonar Jacoco Listener for JUnit to extract coverage details per test -->
<sonar-jacoco-listeners.version>1.4</sonar-jacoco-listeners.version>
<!-- Don't let Sonar execute tests. We will ask it to Maven 'sonar.dynamicAnalysis' is deprecated since version 4.3 and should no longer be used. -->
<!-- <sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis> -->
<!-- The system property jacoco.outputDir needs to be override on the command line
    with an absolute path if you want to merge results from all modules.
    Example in a Jenkisn build where ${WORKSPACE} is defined and your project in the root directory of the workspace :
    mvn clean install -Prun-its,coverage -Djacoco.outputDir=${WORKSPACE}/target
    Note that unfortunately using the following does not work because of
    http://jira.codehaus.org/browse/SONAR-3427:
<jacoco.outputDir>${session.executionRootDirectory}/target/</jacoco.outputDir>
-->
<jacoco.outputDir>${project.build.directory}</jacoco.outputDir>
<!-- Jacoco output file for UTs -->
<jacoco.out.ut.file>jacoco-ut.exec</jacoco.out.ut.file>
<!-- Tells Sonar where the Jacoco coverage result file is located for Unit Tests -->
<sonar.jacoco.reportPath>${jacoco.outputDir}/${jacoco.out.ut.file}</sonar.jacoco.reportPath>
<!-- Jacoco output file for ITs -->
<jacoco.out.it.file>jacoco-it.exec</jacoco.out.it.file>
<!-- Tells Sonar where the Jacoco coverage result file is located for Integration Tests -->
<sonar.jacoco.itReportPath>${jacoco.outputDir}/${jacoco.out.it.file}</sonar.jacoco.itReportPath>
<!-- <sonar.junit.reportsPath>${project.build.directory}/surefire-reports/</sonar.junit.reportsPath> -->
<!-- <sonar.tests>src/test/java</sonar.tests> -->
<!-- === END of Sonar/Reporting settings === -->

For the Jacoco code coverage add the following code to profiles tag

<!-- coverage -->
<profile>
    <id>coverage</id>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <argLine>${jacoco.agent.ut.arg}</argLine>
                    <!-- Specific to generate mapping between tests and covered code -->
                    <properties>
                        <property>
                            <name>listener</name>
                            <value>org.sonar.java.jacoco.JUnitListener</value>
                        </property>
                    </properties>
                    <!-- test failure ignore -->
                    <testFailureIgnore>true</testFailureIgnore>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <configuration>
                    <argLine>-Xmx1024m -XX:MaxPermSize=256m ${jacoco.agent.it.arg}
                    </argLine>
                    <!-- Specific to generate mapping between tests and covered code -->
                    <properties>
                        <property>
                            <name>listener</name>
                            <value>org.sonar.java.jacoco.JUnitListener</value>
                        </property>
                    </properties>
                    <!-- Let's put failsafe reports with surefire to have access to tests 
                        failures/success reports in sonar -->
                    <reportsDirectory>${project.build.directory}/surefire-reports
                    </reportsDirectory>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>${jacoco.version}</version>
                <executions>
                    <!-- Prepares a variable, jacoco.agent.ut.arg, that contains the info 
                        to be passed to the JVM hosting the code being tested. -->
                    <execution>
                        <id>prepare-ut-agent</id>
                        <phase>process-test-classes</phase>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                        <configuration>
                            <destFile>${sonar.jacoco.reportPath}</destFile>
                            <propertyName>jacoco.agent.ut.arg</propertyName>
                            <append>true</append>
                        </configuration>
                    </execution>
                    <!-- Prepares a variable, jacoco.agent.it.arg, that contains the info 
                        to be passed to the JVM hosting the code being tested. -->
                    <execution>
                        <id>prepare-it-agent</id>
                        <phase>pre-integration-test</phase>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                        <configuration>
                            <destFile>${sonar.jacoco.itReportPath}</destFile>
                            <propertyName>jacoco.agent.it.arg</propertyName>
                            <append>true</append>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
    <dependencies>
        <dependency>
            <groupId>org.codehaus.sonar-plugins.java</groupId>
            <artifactId>sonar-jacoco-listeners</artifactId>
            <version>${sonar-jacoco-listeners.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</profile>
<!-- Integraton tests -->
<profile>
    <id>run-its</id>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-failsafe-plugin</artifactId>
                <executions>
                    <execution>
                        <id>integration-test</id>
                        <phase>integration-test</phase>
                        <goals>
                            <goal>integration-test</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>verify</id>
                        <phase>verify</phase>
                        <goals>
                            <goal>verify</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</profile>

When you complete all these steps, you will be able to run SonarQube analysis for each deployment. And plus, you will see test report and test code coverage in SonarQube project page.

You can see the screenshots of the result:

Jenkins Unit Test Report

Image title

Test and Coverage Summary

Image title

Unit Test Report

Image title

Unit Test Coverage Report

Image title

 

 

 

 

Top