Measuring the coverage of your tests

了解如何测量应用程序的测试覆盖率。本指南涵盖:

  • 测量单元测试的覆盖率

  • 测量集成测试的覆盖率

  • 分离单元测试和集成测试的执行

  • 汇总所有测试的覆盖范围

请注意,本地模式不支持代码覆盖。

Prerequisites

include::./_includes/prerequisites.adoc[]* 完成 Testing your application guide

Architecture

本指南中构建的应用程序只是一个依赖于依赖注入以使用服务的 Jakarta REST 端点(hello world)。该服务将使用 JUnit 5 测试,并且该端点将通过 `@QuarkusTest`注释进行注释。

Solution

我们建议你按照下一部分中的说明分步创建应用程序。但是,你可以直接跳到完成的示例。克隆 Git 存储库:git clone $${quickstarts-base-url}.git,或下载一个 $${quickstarts-base-url}/archive/main.zip[存档]。

解决方案位于 tests-with-coverage-quickstart directory

Starting from a simple project and two tests

让我们从使用 Quarkus Maven 插件创建的空应用程序开始:

CLI
quarkus create app {create-app-group-id}:{create-app-artifact-id} \
    --no-code
cd {create-app-artifact-id}

要创建一个 Gradle 项目,添加 --gradle--gradle-kotlin-dsl 选项。 有关如何安装和使用 Quarkus CLI 的详细信息,请参见 Quarkus CLI 指南。

Maven
mvn {quarkus-platform-groupid}:quarkus-maven-plugin:{quarkus-version}:create \
    -DprojectGroupId={create-app-group-id} \
    -DprojectArtifactId={create-app-artifact-id} \
    -DnoCode
cd {create-app-artifact-id}

要创建一个 Gradle 项目,添加 -DbuildTool=gradle-DbuildTool=gradle-kotlin-dsl 选项。

适用于 Windows 用户:

  • 如果使用 cmd,(不要使用反斜杠 \ ,并将所有内容放在同一行上)

  • 如果使用 Powershell,将 -D 参数用双引号引起来,例如 "-DprojectArtifactId={create-app-artifact-id}"

现在,我们将添加所有必要的元素,以创建一个由测试正确覆盖的应用程序。

首先,一个提供 Hello 端点的 Jakarta REST 资源:

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";
    }
}

这个端点使用问候服务:

package org.acme.testcoverage;

import jakarta.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class GreetingService {

    public String greeting(String name) {
        return "hello " + name;
    }

}

该项目还需要一个测试:

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 添加到我们的项目中。为此,我们需要将以下内容添加到构建文件中:

pom.xml
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-jacoco</artifactId>
  <scope>test</scope>
</dependency>
build.gradle
testImplementation("io.quarkus:quarkus-jacoco")

此 Quarkus 扩展负责通过 JaCoCo Maven 插件通常会执行的所有操作,因此不需要其他配置。

同时使用该扩展和插件需要特殊配置,如果你同时添加这两者,将收到有关 class 已被检测到的许多错误。所需的配置在下面详述。

Working with multi-module projects

直到 3.2、`data-file`和 `report-location`总是与模块的构建输出目录相关,这阻止了处理多模块项目,而该项目需要在单个父目录中聚合所有代码覆盖率。从 `3.3`开始,指定 `data-file`或 `report-location`将按原样假定路径。以下是如何设置 `surefire`插件的示例:

<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 直接开箱即用。

有一些配置选项会影响此项:

Unresolved include directive in modules/ROOT/pages/tests-with-coverage.adoc - include::../../../target/quarkus-generated-doc/config/quarkus-jacoco.adoc[]

在处理多模块项目时,如果希望代码覆盖正确工作,需要恰当 indexed上游模块。

Coverage for tests not using @QuarkusTest

Quarkus 自动 JaCoCo 配置仅对使用 `@QuarkusTest`进行注释的测试有效。如果您还希望检查其他测试的覆盖率,则需要返回 JaCoCo maven 插件。

除了在 `pom.xml`中包含 `quarkus-jacoco`扩展外,您还需要以下配置:

pom.xml
<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 此配置告诉它忽略与 `@QuarkusTest`相关的类,因为它们是由 `QuarkusClassLoader`加载的。
build.gradle
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 添加 `jacoco`gradle 插件
2 此配置告诉它忽略与 `@QuarkusTest`相关的类,因为它们是由 `QuarkusClassLoader`加载的。
3 如果您还在使用 quarkus-jacoco`扩展并且至少有一个 `@QuarkusTest,请将此配置设置为 false。 可以跳过默认的 `jacocoTestReport`任务,因为 `quarkus-jacoco`将生成常规单元测试和 `@QuarkusTest`类的合并报告,因为执行数据记录在同一个文件中。

此配置仅当至少运行了一个 @QuarkusTest`时才有效。如果您未使用 `@QuarkusTest,则可以简单地使用 JaCoCo 插件,无需额外的配置即可以标准方式使用它。

Coverage for Integration Tests

要从集成测试中获取代码覆盖率数据,需要满足以下要求:

  • 构建的工件是一个 jar(而不是容器或本机二进制文件)。

  • 需要在构建工具中配置 JaCoCo。

  • 必须使用 quarkus.package.write-transformed-bytecode-to-build-output`设置应用程序以构建,该设置设置为 `true

设置 `quarkus.package.write-transformed-bytecode-to-build-output=true`时应谨慎,并且仅在后续构建是在干净的环境中完成时才设置 - 即已完全删除构建工具的输出目录。

在 `pom.xml`中,您可以添加 JaCoCo 的以下插件配置。这将把集成测试数据追加到与单元测试相同的目标文件中,在集成测试完成后重新构建 JaCoCo 报告,从而生成全面的代码覆盖报告。

<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 所有执行应在相同的 `&lt;plugin&gt;`定义中,因此请确保将它们全部串联起来。

为了使用 JaCoCo 代理以 jar 形式运行集成测试,请将以下内容添加到您的 `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 配置文件。

Setting coverage thresholds

您可以使用 JaCoCo Maven 插件设置代码覆盖率阈值。请注意元素 <dataFile>${project.build.directory}/jacoco-quarkus.exec</dataFile>。您必须根据自己对 `quarkus.jacoco.data-file`的选择进行设置。

pom.xml
<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 所有执行应在相同的 `<plugin>`定义中,因此请确保将它们全部串联起来。
build.gradle
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

排除类不进行验证任务的配置如下:

jacocoTestCoverageVerification {
    afterEvaluate { 1
        classDirectories.setFrom(files(classDirectories.files.collect { 2
            fileTree(dir: it, exclude: [
                    "org/example/package/**/*" 3
            ])
        }))
    }
}
1 在 Gradle 中,需要在评估阶段之后读取 classDirectories
2 目前,Gradle JaCoCo 中存在一个 bug,需要以这种方式指定 excludes - [role="bare"][role="bare"]https://github.com/gradle/gradle/issues/14760 。一旦解决此问题,排除
3 排除 org/example/package 包中的所有类

Conclusion

您现在拥有研究测试覆盖率所需的所有信息!但请记住,某些未覆盖的代码肯定测试得不够好。但部分已覆盖的代码不一定测试得很好 well 。请务必编写良好的测试!