Measuring the coverage of your tests
了解如何测量应用程序的测试覆盖率。本指南涵盖:
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.
Prerequisites
include::{includes}/prerequisites.adoc[]* 完成 Testing your application guide后
Unresolved directive in tests-with-coverage.adoc - include::{includes}/prerequisites.adoc[] * Having completed the Testing your application guide
Architecture
本指南中构建的应用程序只是一个依赖于依赖注入以使用服务的 Jakarta REST 端点(hello world)。该服务将使用 JUnit 5 测试,并且该端点将通过 `@QuarkusTest`注释进行注释。
The application built in this guide is just a Jakarta REST 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.
Solution
我们建议你按照下一部分中的说明分步创建应用程序。但是,你可以直接跳到完成的示例。克隆 Git 存储库:git clone {quickstarts-clone-url}
,或下载一个 {quickstarts-archive-url}[存档]。
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 {quickstarts-clone-url}
, or download an {quickstarts-archive-url}[archive].
解决方案位于 tests-with-coverage-quickstart
directory。
The solution is located in the tests-with-coverage-quickstart
directory.
Starting from a simple project and two tests
让我们从使用 Quarkus Maven 插件创建的空应用程序开始:
Let’s start from an empty application created with the Quarkus Maven plugin:
Unresolved directive in tests-with-coverage.adoc - include::{includes}/devtools/create-app.adoc[]
现在,我们将添加所有必要的元素,以创建一个由测试正确覆盖的应用程序。
Now we’ll be adding all the elements necessary to have an application that is properly covered with tests.
首先,一个提供 Hello 端点的 Jakarta REST 资源:
First, a Jakarta REST resource serving a hello endpoint:
package org.acme.testcoverage;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.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(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 jakarta.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));
}
}
Setting up JaCoCo
现在,我们需要将 JaCoCo 添加到我们的项目中。为此,我们需要将以下内容添加到构建文件中:
Now we need to add JaCoCo to our project. To do this we need to add the following to the build file:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jacoco</artifactId>
<scope>test</scope>
</dependency>
testImplementation("io.quarkus:quarkus-jacoco")
此 Quarkus 扩展负责通过 JaCoCo Maven 插件通常会执行的所有操作,因此不需要其他配置。
This Quarkus extension takes care of everything that would usually be done via the JaCoCo Maven plugin, so no additional config is required.
同时使用该扩展和插件需要特殊配置,如果你同时添加这两者,将收到有关 class 已被检测到的许多错误。所需的配置在下面详述。
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.
Working with multi-module projects
直到 3.2
、`data-file`和 `report-location`总是与模块的构建输出目录相关,这阻止了处理多模块项目,而该项目需要在单个父目录中聚合所有代码覆盖率。从 `3.3`开始,指定 `data-file`或 `report-location`将按原样假定路径。以下是如何设置 `surefire`插件的示例:
Up until 3.2
, data-file
and report-location
were always relative to the module’s build output directory, which prevented from
working with multi-module projects where you want to aggregate all coverages into a single parent directory. Starting in 3.3
,
specifying a data-file
or report-location
will assume the path as is. Here is an example on how to set up the surefire
plugin:
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<quarkus.jacoco.data-file>${maven.multiModuleProjectDirectory}/target/jacoco.exec</quarkus.jacoco.data-file>
<quarkus.jacoco.reuse-data-file>true</quarkus.jacoco.reuse-data-file>
<quarkus.jacoco.report-location>${maven.multiModuleProjectDirectory}/target/coverage</quarkus.jacoco.report-location>
</systemPropertyVariables>
</configuration>
</plugin
Running the tests with coverage
运行 mvn verify
,测试将运行并且结果将最终出现在 `target/jacoco-reports`中。这就是全部内容,`quarkus-jacoco`扩展允许 JaCoCo 直接开箱即用。
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:
Unresolved directive in tests-with-coverage.adoc - include::{generated-dir}/config/quarkus-jacoco.adoc[]
Coverage for tests not using @QuarkusTest
Quarkus 自动 JaCoCo 配置仅对使用 `@QuarkusTest`进行注释的测试有效。如果您还希望检查其他测试的覆盖率,则需要返回 JaCoCo maven 插件。
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.
除了在 `pom.xml`中包含 `quarkus-jacoco`扩展外,您还需要以下配置:
In addition to including the quarkus-jacoco
extension in your pom.xml
you will need the following config:
<project>
<build>
<plugins>
...
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<executions>
<execution>
<id>default-prepare-agent</id>
<goals>
<goal>prepare-agent</goal>
</goals>
<configuration>
<exclClassLoaders>*QuarkusClassLoader</exclClassLoaders> 1
<destFile>${project.build.directory}/jacoco-quarkus.exec</destFile>
<append>true</append>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
1 | This config tells it to ignore @QuarkusTest related classes, as they are loaded by QuarkusClassLoader |
plugins {
id 'jacoco' 1
}
test {
finalizedBy jacocoTestReport
jacoco {
excludeClassLoaders = ["*QuarkusClassLoader"] 2
destinationFile = layout.buildDirectory.file("jacoco-quarkus.exec").get().asFile 2
}
jacocoTestReport.enabled = false 3
}
1 | Add the jacoco gradle plugin |
2 | This config tells it to ignore @QuarkusTest related classes, as they are loaded by QuarkusClassLoader |
3 | Set this config to false if you are also using the quarkus-jacoco extension and have at least one @QuarkusTest . The default jacocoTestReport task can be skipped since quarkus-jacoco will generate the combined report of regular unit tests and @QuarkusTest classes since the execution data is recorded in the same file. |
此配置仅当至少运行了一个 @QuarkusTest`时才有效。如果您未使用 `@QuarkusTest
,则可以简单地使用 JaCoCo 插件,无需额外的配置即可以标准方式使用它。
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.
Coverage for Integration Tests
要从集成测试中获取代码覆盖率数据,需要满足以下要求:
To get code coverage data from integration tests, the following 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
设置 `quarkus.package.write-transformed-bytecode-to-build-output=true`时应谨慎,并且仅在后续构建是在干净的环境中完成时才设置 - 即已完全删除构建工具的输出目录。
Setting quarkus.package.write-transformed-bytecode-to-build-output=true
should be done with caution and only if subsequent builds are done in a clean environment - i.e. the build tool’s output directory has been completely cleaned.
在 `pom.xml`中,您可以添加 JaCoCo 的以下插件配置。这将把集成测试数据追加到与单元测试相同的目标文件中,在集成测试完成后重新构建 JaCoCo 报告,从而生成全面的代码覆盖报告。
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>
<version>${jacoco.version}</version>
<executions>
... 1
<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</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>
1 | All executions should be in the same <plugin> definition so make sure you concatenate all of them. |
为了使用 JaCoCo 代理以 jar 形式运行集成测试,请将以下内容添加到您的 `pom.xml`中。
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>
为 `quarkus.test.arg-line`共享相同的值可能会中断测试不同类型 Quarkus 工件的集成测试运行。在这种情况中,建议使用 Maven 配置文件。
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.
Setting coverage thresholds
您可以使用 JaCoCo Maven 插件设置代码覆盖率阈值。请注意元素 <dataFile>${project.build.directory}/jacoco-quarkus.exec</dataFile>
。您必须根据自己对 `quarkus.jacoco.data-file`的选择进行设置。
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>
... 1
<execution>
<id>jacoco-check</id>
<goals>
<goal>check</goal>
</goals>
<phase>post-integration-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>
1 | All executions should be in the same <plugin> definition so make sure you concatenate all of them. |
jacocoTestCoverageVerification {
executionData.setFrom("$project.buildDir/jacoco-quarkus.exec")
violationRules {
rule {
limit {
counter = 'INSTRUCTION'
value = 'COVEREDRATIO'
minimum = 0.80
}
limit {
counter = 'BRANCH'
value = 'COVEREDRATIO'
minimum = 0.72
}
}
}
}
check.dependsOn jacocoTestCoverageVerification
排除类不进行验证任务的配置如下:
Excluding classes from the verification task can be configured as following:
jacocoTestCoverageVerification {
afterEvaluate { 1
classDirectories.setFrom(files(classDirectories.files.collect { 2
fileTree(dir: it, exclude: [
"org/example/package/**/*" 3
])
}))
}
}
1 | classDirectories needs to be read after evaluation phase in Gradle |
2 | Currently, there is a bug in Gradle JaCoCo which requires the excludes to be specified in this manner - [role="bare"]https://github.com/gradle/gradle/issues/14760. Once this issue is fixed, excludes |
3 | Exclude all classes in org/example/package package |
Conclusion
您现在拥有研究测试覆盖率所需的所有信息!但请记住,某些未覆盖的代码肯定测试得不够好。但部分已覆盖的代码不一定测试得很好 well 。请务必编写良好的测试!
You now have all the information you need to study the coverage of your tests! But remember, some code that is not covered is certainly not well tested. But some code that is covered is not necessarily well tested. Make sure to write good tests!