Fork me on GitHub

Jacoco in action with Maven

Jacoco-Maven (abbreviation for Java Code Coverage) plugin is an open-source code coverage tool for Java. It creates code coverage reports and integrates well with IDEs(Integrated development environments) like Eclipse IDE.

The purpose of this small project (built on top of TestsExecution) is to utilize the Jacoco plugin for code coverage reports.

Goals utilization

Following goals of the Jacoco plugin can be used to generate coverage reports:

  • The report goal can be used to generate the coverage report for unit tests only with little bit of config.
  • The report-integration goal can be used to generate the coverage report for integration tests only with little bit of config.
  • The merge goal can be used to generate the combined/merged coverage report for all the tests.

Individual reports for the code covered by unit tests as well as integration tests will be generated. And then, a combined coverage report will also be generated to show the total coverage. These generated coverage reports will then be included in the project reports through mvn site.

Running the tests

See TestsExecution project

Jacoco Code Coverage Reports

  • Code coverage reports for
    • units tests are generated right after the execution of unit tests and can be found in target/site/jacoco directory.
    • integration tests are generated right after the execution of integration tests and can be found in target/site/jacoco-it directory.
    • Combined or merged report for both the unit and the integration tests' will also be generated right after the execution of integration tests and can be found in target/site/jacoco-merged directory.
  • Once the reports are generated, point a browser at the output in any of the following directories to see:
    • unit tests' coverage report: target/site/jacoco/index.html
    • integration tests' coverage report: target/site/jacoco-it/index.html
    • all tests' coverage report: target/site/jacoco-merged/index.html

How to set up JaCoCo Plugin with Maven?

Here are the steps to integrate JaCoCo Maven plugin with a Maven project:

  • Declare the Jacoco-Maven plugin in the pom.xml file as follows:
    <build>
        <plugins>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>${jacoco-maven-plugin.version}</version>
            </plugin>
        </plugins>
    </build>
    
  • The above declaration won't be of much use without further configuration, so after the version tag, we add the executions tag. This tag prepares the properties or execution to point to the JaCoCo agent and is passed as a VM (in this case, JVM) argument.
  • The bare minimum for running simple unit tests (mvn test) is to set up a prepare-agent goal and report goal. The following config will run the JaCoCo report goal during the test phase of the build lifecycle:
    <plugin>
        <groupId>org.jacoco</groupId>
        <artifactId>jacoco-maven-plugin</artifactId>
        <version>${jacoco-maven-plugin.version}</version>
        <executions>
            <execution>
                <id>before-unit-test-execution</id>
                <goals>
                    <goal>prepare-agent</goal>
                </goals>
            </execution>
            <execution>
                <id>after-unit-test-execution</id>
                <phase>test</phase>
                <goals>
                    <goal>report</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
    
    • Prepare-agent goal:

      The prepare-agent goal prepares the JaCoCo runtime agent to record the execution data. It records the number of lines executed, backtracked, etc. By default, the execution data is written to the file target/jacoco.exec.

Report goal:

The report goal creates code coverage reports from the execution data recorded by the JaCoCo runtime agent. Since we have specified the phase property, so the reports will be created after the compilation of the test phase of the build lifecycle. By default, the execution data is read from the file target/jacoco.exec, and the code coverage report is written to the directory target/site/jacoco/index.html.

Configuration for unit tests with custom paths

The following configuration inside the <id>before-unit-test-execution</id> execution block, will set the data execution file for the unit tests' coverage to be stored in target/jacoco-execs/ut.exec file and we achieve that by overwriting the surefire.jacoco.exec.file.name.arg property used while running unit tests.

<configuration>
    <destFile>target/jacoco-execs/ut.exec</destFile>
    <propertyName>surefire.jacoco.exec.file.name.arg</propertyName>
</configuration>

We also need to tell where to find the data execution file while running the report goal, so we'll use the following configuration in after-unit-test-execution execution block. We've also set the outputDirectory which is an optional setting, and thats where the generated report will be stored.

<configuration>
    <destFile>target/jacoco-execs/ut.exec</destFile>
    <outputDirectory>${jacoco.ut.reporting.directory}</outputDirectory>
</configuration>

So, the full configuration for running the unit tests will look like this and we're all set as far as running and generating report for unit tests:

<execution>
    <id>before-unit-test-execution</id>
    <goals>
        <goal>prepare-agent</goal>
    </goals>
    <configuration>
        <destFile>target/jacoco-execs/ut.exec</destFile>
        <propertyName>surefire.jacoco.exec.file.name.arg</propertyName>
    </configuration>
</execution>
<execution>
    <id>after-unit-test-execution</id>
    <phase>test</phase>
    <goals>
        <goal>report</goal>
    </goals>
    <configuration>
        <destFile>target/jacoco-execs/ut.exec</destFile>
        <outputDirectory>${jacoco.ut.reporting.directory}</outputDirectory>
    </configuration>
</execution>

Configuration for integration tests

Now we gonna need the exact same configuration for integration tests as follows:

<execution>
    <id>before-integration-test-execution</id>
    <phase>pre-integration-test</phase>
    <goals>
        <goal>prepare-agent-integration</goal>
    </goals>
    <configuration>
        <destFile>target/jacoco-execs/it.exec</destFile>
        <propertyName>failsafe.jacoco.exec.file.name.arg</propertyName>
    </configuration>
</execution>
<execution>
    <id>after-integration-test-execution</id>
    <phase>post-integration-test</phase>
    <goals>
        <goal>report-integration</goal>
    </goals>
    <configuration>
        <destFile>target/jacoco-execs/it.exec</destFile>
        <outputDirectory>${jacoco.it.reporting.directory}</outputDirectory>
    </configuration>
</execution>

In the above configuration, instead of using prepare-agent goal and report goal, we used prepare-agent-integration goal and report integration goal which are more related to the execution of integration tests.

The above config will run the JaCoCo report integration goal during the post-integration-test phase of the build lifecycle.

Merge the data/exec files

Add the following config after <id>after-integration-test-execution</id> execution block:

<execution>
    <id>merge-unit-and-integration</id>
    <phase>post-integration-test</phase>
    <goals>
        <goal>merge</goal>
    </goals>
    <configuration>
        <destFile>target/jacoco-execs/merged.exec</destFile>
        <fileSets>
            <fileSet>
                <directory>target/jacoco-execs</directory>
                <includes>
                    <include>ut.exec</include>
                    <include>it.exec</include>
                </includes>
            </fileSet>
        </fileSets>
    </configuration>
</execution>

In the above configuration, we've bound the merge goal to the post-integration-test phase of the build lifecycle, which will merge the included exec files into one single combined exec file.

Generate the merged coverage report

Add the following config after <id>merge-unit-and-integration</id> execution block:

<execution>
    <id>create-merged-report</id>
    <phase>post-integration-test</phase>
    <goals>
        <goal>report</goal>
    </goals>
    <configuration>
        <dataFile>${jacoco.merged.exec}</dataFile>
        <outputDirectory>${jacoco.merged.reporting.directory}</outputDirectory>
    </configuration>
</execution>

In the above configuration, we've bound the report goal to the post-integration-test phase of the build lifecycle, which will generate the combined coverage report from the provided exec file.

Add code coverage check

Add the following config after <id>create-merged-report</id> execution block:

<execution>
    <id>jacoco-check</id>
    <phase>verify</phase>
    <goals>
        <goal>check</goal>
    </goals>
    <configuration>
        <rules>
            <rule>
                <element>CLASS</element>
                <excludes>
                    <exclude>*Test</exclude>
                    <exclude>*IT</exclude>
                </excludes>
                <limits>
                    <limit>
                        <counter>LINE</counter>
                        <value>COVEREDRATIO</value>
                        <minimum>${jacoco.minimum.line.coverage}</minimum>
                    </limit>
                </limits>
            </rule>
        </rules>
        <dataFile>${jacoco.merged.exec}</dataFile>
    </configuration>
</execution>

In the above configuration, we've bound the check goal to the verify phase of the build lifecycle, which will perform the verification checks according the specified rules on the provided exec file, and will fail the build if the checks are not met.

How to set up JaCoCo Plugin with Maven for parallel execution?

More configuration is required if we want to run the unit tests and integration tests parallel/simultaneously on multiple machines. For example, in CI/CD environment, we usually want parallel execution of jobs as much as possible to speed up the build process. So, in order to achieve that, let's first have separate execution data files for unit and integration tests.

To achieve the parallel execution, use the following commands:

  • One job can run mvn test -P ut, which will generate a file named jacoco.exec or jacoco-ut.exec, store or upload that file somewhere for later use
  • Another job can run mvn integration-test -P it, which will generate a file named jacoco-it.exec, store or upload that file somewhere for later use. NOTE: you might be wondering why we just didn't do mvn verify -P nt, that's because all the data files won't be present and hence the generated report won't be correct
  • Then in the report generation job:
    • Download the jacoco-ut.exec and jacoco-it.exec data files
    • Place them in the target/jacoco-execs directory or whichever directory you're using for the data files
    • Run the mvn post-integration-test -P nt or mvn verify -P nt, and the generated reports will be there in the target/site directory