← Back to Articles

Integration Test Code Coverage with SonarQube and Jacoco

Tom Gregory
Tom Gregory
Apr 18, 20226 min read

It's hepful to separate integration tests from unit tests in a Java project to run them independently. But how do you generate a combined code coverage metric? In this article you'll discover exactly how, with a full example project which publishes Jacoco code coverage stats to SonarQube.

Code coverage for unit tests

A code coverage metric tells you what percentage of your proudction code is covered by tests. The higher the better.

Jacoco is a tool which generates such metrics.

Generating code coverage data based on unit tests is straightforward in Gradle thanks to the jacoco plugin. The data can then be published to the static analysis tool SonarQube via the sonarqube plugin.

If you haven't tried out this approach yet, then I suggest first reading How to Measure Code Coverage Using SonarQube and Jacoco which explains the step-by-step process.

If you're in a hurry, here's how to configure the Gradle build.gradle build script to integrate with Jacoco and SonarQube for unit tests only.

plugins {
    id 'java'
    id 'jacoco'
    id 'org.sonarqube' version '3.3'
}
 
jacocoTestReport {
    reports {
        xml.required = true
    }
    dependsOn test
}
 
sonarqube {
    properties {
        property 'sonar.host.url', 'http://localhost:9000'
    }
}
 
tasks.named('sonarqube').configure {
    dependsOn jacocoTestReport
}
 
// repositories, dependencies, etc.

Here are the highlights:

With this in place, we simply run ./gradlew sonarqube to send code coverage data to the SonarQube server.

SonarQube code coverage

Viewing code coverage data in SonarQube

But if it's so easy to generate a code coverage metric for unit tests, how can we include integration tests too?

To understand how, let's first explore how integration testing works in Gradle.

Integration testing in Gradle

Creating a separate integration test suite is very simple since Gradle 7.3, thanks to the JVM Test Suite plugin.

The plugin is applied automatically by the java plugin, and helps setup your project so your integration tests can:

The plugin can generate any type of test suite you like, but here's how you configure it for integration tests.

testing {
    suites {
        integrationTest(JvmTestSuite) {
            dependencies {
                implementation project
            }
        }
    }
}

Now when we run ./gradlew integrationTest Gradle executes any tests in src/integrationTest/java.

This will pass or fail the build based on the outcome of the tests, just like when you run unit tests with ./gradlew test.

So now we know how to run integration tests, let's get into the nitty gritty of how to generate and pubilsh code coverage metrics for them.

Execution data for integration tests

Thankfully a big piece of the puzzle is already solved for us simply by including the java and jacoco plugins. With this plugin combo Gradle automatically enriches any task of type Test to also produce Jacoco code coverage execution data.

To see this in action we'll use this sample project on GitHub which has:

  1. a class MathService with a MathServiceTest unit test in src/test/java

  2. a class Appliciation with an ApplicationTest integration test in src/integrationTest/java

The sample project applies java and jacoco plugins, so when we run ./gradlew integrationTest Gradle produces an integrationTest.exec file.

This is the Jacoco code coverage execution data in raw binary format. Running ./gradlew test produces a similar file at build/jacoco/test.exec.

But this binary file is no good for SonarQube, which prefers muching data in XML format. 😋

Generate a combined XML test report

The main Gradle task that the jacoco plugin registers is jacocoTestReport. This by default produces an HTML report, but can be configured to also generate an XML report.

jacocoTestReport {
    reports {
        xml.required = true
    }
    dependsOn test
}

With this in place, when we run ./gradlew jacocoTestReport an XML report is produced at build/reports/jacoco/test/jacocoTestReport.xml.

The XML file is a bit of a beast, with detailed code coverage data. But here's how a minified version looks.

Right now the snippet below shows us that the main method of Application.java has not been covered by tests (covered="0").

            <method name="main" desc="(\[Ljava/lang/String;)V" line="5">
                <counter type="INSTRUCTION" missed="4" covered="0"/>
                <counter type="LINE" missed="2" covered="0"/>
                <counter type="COMPLEXITY" missed="1" covered="0"/>
                <counter type="METHOD" missed="1" covered="0"/>
            </method>

Why? Simply that the generated XML report by default only takes into account test.exec and not integrationTest.exec.

So let's combine the .exec files into a single XML report with a surprisingly simple change to the build script.

jacocoTestReport {
    executionData integrationTest
    reports {
        xml.required = true
    }
    dependsOn test, integrationTest
}

Now when we run ./gradlew jacocoTestReport the report says that Application.main is covered by tests (covered="4" etc.). Neat!

            <method name="main" desc="(\[Ljava/lang/String;)V" line="5">
                <counter type="INSTRUCTION" missed="0" covered="4"/>
                <counter type="LINE" missed="0" covered="2"/>
                <counter type="COMPLEXITY" missed="0" covered="1"/>
                <counter type="METHOD" missed="0" covered="1"/>
            </method>

Send coverage data to SonarQube

Let's send the Jacoco code coverage data off to SonarQube. We'll start the SonarQube server with docker-compose up, which uses the docker-compose.yml file from the sample project.

Since the sample project has the org.sonarqube plugin applied, all we have to do now is run ./gradlew sonarqube to send the test coverage data to SonarQube.

Viewing the report in a locally running SonarQube shows us the code coverage data.

Final thoughts

As you've seen, getting Gradle to publish integration test code coverage data to SonarQube is just a case of configuring the jacocoTestReport task to include the integrationTest.exec execution data. You may like to include other suites of tests too (e.g. end2end), depending on your use case.

Tom Gregory
Who is Tom Gregory?

After years in the development world, I decided to step away from the traditional career path to build my own software and turn my ideas into a livelihood. Here, I share insights from my journey, aiming to inspire others to carve their own path to success.