Learn how to measure the test coverage of your application. This guide covers:
-
Measuring the coverage of your Unit Tests
-
Measuring the coverage of your Integration Tests
-
Separating the execution of your Unit Tests and Integration Tests
-
Consolidating the coverage for all your tests
Please note that code coverage is not supported in native mode.
1. Prerequisites
To complete this guide, you need:
-
less than 15 minutes
-
an IDE
-
JDK 11+ installed with JAVA_HOME configured appropriately
-
Apache Maven 3.8.1
-
Having completed the Testing your application guide
2. Architecture
The application built in this guide is just a JAX-RS endpoint (hello world) that relies on dependency injection to use a service.
The service will be tested with JUnit 5 and the endpoint will be annotated via a @QuarkusTest
annotation.
3. Solution
We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.
Clone the Git repository: git clone https://github.com/quarkusio/quarkus-quickstarts.git
, or download an archive.
The solution is located in the tests-with-coverage-quickstart
directory.
4. Starting from a simple project and two tests
Let’s start from an empty application created with the Quarkus Maven plugin:
mvn io.quarkus.platform:quarkus-maven-plugin:create \
-DprojectGroupId=org.acme \
-DprojectArtifactId=tests-with-coverage-quickstart
cd tests-with-coverage-quickstart
Now we’ll be adding all the elements necessary to have an application that is properly covered with tests.
First, an application serving a hello endpoint:
package org.acme.testcoverage;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("/hello")
public class GreetingResource {
private final GreetingService service;
@Inject
public GreetingResource(GreetingService service) {
this.service = service;
}
@GET
@Produces(MediaType.TEXT_PLAIN)
@Path("/greeting/{name}")
public String greeting(@PathParam("name") String name) {
return service.greeting(name);
}
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "hello";
}
}
This endpoint uses a greeting service:
package org.acme.testcoverage;
import javax.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class GreetingService {
public String greeting(String name) {
return "hello " + name;
}
}
The project will also need a test:
package org.acme.testcoverage;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Tag;
import java.util.UUID;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
@QuarkusTest
public class GreetingResourceTest {
@Test
public void testHelloEndpoint() {
given()
.when().get("/hello")
.then()
.statusCode(200)
.body(is("hello"));
}
@Test
public void testGreetingEndpoint() {
String uuid = UUID.randomUUID().toString();
given()
.pathParam("name", uuid)
.when().get("/hello/greeting/{name}")
.then()
.statusCode(200)
.body(is("hello " + uuid));
}
}
5. Setting up Jacoco
Now we need to add Jacoco to our project. To do this we need to add the following to the pom.xml
dependencies section:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jacoco</artifactId>
<scope>test</scope>
</dependency>
This Quarkus extension takes care of everything that would usually be done via the Jacoco maven plugin, so no additional config is required.
Using both the extension and the plugin requires special configuration, if you add both you will get lots of errors about classes already being instrumented. The configuration needed is detailed below. |
6. Running the tests with coverage
Run mvn verify
, the tests will be run and the results will end up in target/jacoco-reports
. This is all that is needed,
the quarkus-jacoco
extension allows Jacoco to just work out of the box.
There are some config options that affect this:
Configuration property fixed at build time - All other configuration properties are overridable at runtime
Type |
Default |
|
---|---|---|
The jacoco data file |
string |
|
Whether to reuse ( |
boolean |
|
If Quarkus should generate the Jacoco report |
boolean |
|
Encoding of the generated reports. |
string |
|
Name of the root node HTML report pages. |
string |
|
Footer text used in HTML report pages. |
string |
|
Encoding of the source files. |
string |
|
A list of class files to include in the report. May use wildcard characters (* and ?). When not specified everything will be included. |
list of string |
|
A list of class files to exclude from the report. May use wildcard characters (* and ?). When not specified nothing will be excluded. |
list of string |
|
The location of the report files. |
string |
|
7. Coverage for tests not using @QuarkusTest
The Quarkus automatic Jacoco config will only work for tests that are annotated with @QuarkusTest
. If you want to check
the coverage of other tests as well then you will need to fall back to the Jacoco maven plugin.
In addition to including the quarkus-jacoco
extension in your pom you will need the following config:
<project>
<build>
<plugins>
...
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<executions>
<execution>
<id>default-prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
<configuration>
<exclClassLoaders>*QuarkusClassLoader</exclClassLoaders> (1)
</configuration>
</execution>
<execution>
<id>default-prepare-agent-integration</id> (2)
<goals>
<goal>prepare-agent-integration</goal>
</goals>
<configuration>
<exclClassLoaders>*QuarkusClassLoader</exclClassLoaders>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
1 | This config tells it to ignore @QuarkusTest related classes, as they are loaded by QuarkusClassLoader |
2 | This is only needed if you are using Failsafe to run integration tests |
This config will only work if at least one @QuarkusTest is being run. If you are not using @QuarkusTest then
you can simply use the Jacoco plugin in the standard manner with no additional config.
|
7.1. Coverage for Integration Tests
To get code coverage data from integration tests, the following need to be requirements need to be met:
-
The built artifact is a jar (and not a container or native binary).
-
Jacoco needs to be configured in your build tool.
-
The application must have been built with
quarkus.package.write-transformed-bytecode-to-build-output
set totrue
Setting quarkus.package.write-transformed-bytecode-to-build-output=true should be done with a caution and only if subsequent builds are done in a clean environment - i.e. the build tool’s output directory has been completely cleaned.
|
In the pom.xml
, you can add the following plugin configuration for Jacoco. This will append integration test data into the same destination file as unit tests,
re-build the jacoco report after the integration tests are complete, and thus produce a comprehensive code-coverage report.
<build>
...
<plugins>
...
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<executions>
<execution>
<id>default-prepare-agent-integration</id>
<goals>
<goal>prepare-agent-integration</goal>
</goals>
<configuration>
<destFile>${project.build.directory}/jacoco-quarkus.exec</destFile>
<append>true</append>
</configuration>
</execution>
<execution>
<id>report-it</id>
<phase>post-integration-test</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<dataFile>${project.build.directory}/jacoco-quarkus.exec</dataFile>
<outputDirectory>${project.build.directory}/jacoco-report</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
...
</plugins>
...
</build>
In order to run the integration tests as a jar with the Jacoco agent, add the following to your pom.xml
.
<build>
...
<plugins>
...
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
<quarkus.test.arg-line>${argLine}</quarkus.test.arg-line>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
</plugin>
...
</plugins>
...
</build>
Sharing the same value for quarkus.test.arg-line might break integration test runs that test different types of Quarkus artifacts. In such cases, the use of maven profiles is advised.
|
8. Setting coverage thresholds for the Maven build
You can set thresholds for code coverage using the Jacoco Maven plugin. Note the element <dataFile>${project.build.directory}/jacoco-quarkus.exec</dataFile>
. You must set it matching your choice for quarkus.jacoco.data-file
.
<build>
...
<plugins>
...
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<executions>
<execution>
<id>jacoco-check</id>
<goals>
<goal>check</goal>
</goals>
<phase>test</phase>
<configuration>
<dataFile>${project.build.directory}/jacoco-quarkus.exec</dataFile>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.8</minimum>
</limit>
<limit>
<counter>BRANCH</counter>
<value>COVEREDRATIO</value>
<minimum>0.72</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
...
</plugins>
...
</build>