编辑此页面

测量您的测试覆盖率

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

  • 测量单元测试的覆盖率

  • 测量集成测试的覆盖率

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

  • 整合所有测试的覆盖率

请注意,原生模式不支持代码覆盖率。

1. 前提条件

要完成本指南,您需要

  • 大约 15 分钟

  • 一个 IDE

  • 已安装 JDK 17+ 并正确配置了 JAVA_HOME

  • Apache Maven 3.9.9

  • 如果您想使用它,可以选择 Quarkus CLI

  • 如果您想构建本机可执行文件(或者如果您使用本机容器构建,则为 Docker),可以选择安装 Mandrel 或 GraalVM 并进行适当的配置

  • 已完成测试你的应用程序指南

2. 架构

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

3. 解决方案

我们建议你按照以下部分的说明逐步创建应用程序。但是,你可以直接转到已完成的示例。克隆 Git 仓库:git clone https://github.com/quarkusio/quarkus-quickstarts.git,或下载存档

解决方案位于 tests-with-coverage-quickstart 目录

4. 从一个简单的项目和两个测试开始

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

CLI
quarkus create app org.acme:tests-with-coverage-quickstart \
    --extension='rest' \
    --no-code
cd tests-with-coverage-quickstart

要创建 Gradle 项目,请添加 --gradle--gradle-kotlin-dsl 选项。

有关如何安装和使用 Quarkus CLI 的更多信息,请参阅 Quarkus CLI 指南。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:3.24.4:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=tests-with-coverage-quickstart \
    -Dextensions='rest' \
    -DnoCode
cd tests-with-coverage-quickstart

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

对于 Windows 用户

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

  • 如果使用 Powershell,请将 -D 参数用双引号括起来,例如 "-DprojectArtifactId=tests-with-coverage-quickstart"

现在,我们将添加所有必要的元素,以使应用程序被测试正确覆盖。

首先,一个 Jakarta REST 资源提供一个 hello 端点

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

此端点使用 greeting 服务

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

5. 设置 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 插件完成的所有操作,因此无需额外的配置。

同时使用扩展和插件需要特殊配置,如果同时添加两者,你将收到大量关于类已经被检测的错误。所需的配置在下面详细说明。

6. 使用多模块项目

直到 3.2data-filereport-location 始终相对于模块的构建输出目录,这阻止了使用多模块项目,在这些项目中,你希望将所有覆盖率聚合到单个父目录中。从 3.3 开始,指定 data-filereport-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
如果你需要配置 Surefire 插件的 argLine 属性(例如,用于设置内存参数),则需要使用Maven 延迟属性评估,否则 Jacoco 代理将不会被正确添加,并且常规 JUnit 测试和 Quarkus ComponentTest 将不会被覆盖。示例:<argLine>@{argLine} -your -extra -arguments</argLine>

7. 运行带覆盖率的测试

运行 mvn verify,测试将被运行,结果将最终出现在 target/jacoco-reports 中。这就是所需要的全部,quarkus-jacoco 扩展允许 JaCoCo 只是开箱即用。

有一些配置选项会影响这一点

构建时固定的配置属性 - 所有其他配置属性都可以在运行时覆盖

配置属性

类型

默认

是否启用 Jacoco 扩展。

环境变量:QUARKUS_JACOCO_ENABLED

显示更多

布尔值

true

Jacoco 数据文件。路径可以是相对路径(相对于模块)或绝对路径。

环境变量:QUARKUS_JACOCO_DATA_FILE

显示更多

字符串

target/jacoco-quarkus.exec

是否在每次运行时重用 (true) 或删除 (false) Jacoco 数据文件。

环境变量:QUARKUS_JACOCO_REUSE_DATA_FILE

显示更多

布尔值

false

Quarkus 是否应生成 Jacoco 报告

环境变量:QUARKUS_JACOCO_REPORT

显示更多

布尔值

true

生成的报告的编码。

环境变量:QUARKUS_JACOCO_OUTPUT_ENCODING

显示更多

字符串

UTF-8

根节点 HTML 报告页面的名称。

环境变量:QUARKUS_JACOCO_TITLE

显示更多

字符串

${quarkus.application.name}

HTML 报告页面中使用的页脚文本。

环境变量:QUARKUS_JACOCO_FOOTER

显示更多

字符串

源文件的编码。

环境变量:QUARKUS_JACOCO_SOURCE_ENCODING

显示更多

字符串

UTF-8

要包含在报告中的类文件列表。可以使用通配符(* 和 ?)。如果未指定,则将包含所有内容。

例如

  • **/fo/**/* 针对 fo 和子包下的所有类

  • **/bar/* 针对 bar 下的所有类

  • **/*BAR*.class 针对名称中包含 BAR 的类,而不管路径如何

环境变量:QUARKUS_JACOCO_INCLUDES

显示更多

字符串列表

**

要从报告中排除的类文件列表。可以使用通配符(* 和 ?)。如果未指定,则不会排除任何内容。

例如

  • **/fo/**/* 针对 fo 和子包下的所有类

  • **/bar/* 针对 bar 下的所有类

  • **/*BAR*.class 针对名称中包含 BAR 的类,而不管路径如何

环境变量:QUARKUS_JACOCO_EXCLUDES

显示更多

字符串列表

报告文件的位置。路径可以是相对路径(相对于模块)或绝对路径。

环境变量:QUARKUS_JACOCO_REPORT_LOCATION

显示更多

字符串

target/jacoco-report

在使用多模块项目时,为了使代码覆盖率正常工作,需要正确地索引上游模块。

8. 不使用 @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 插件,而无需额外的配置。

8.1. 集成测试的覆盖率

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

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

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

  • 必须使用设置为 truequarkus.package.write-transformed-bytecode-to-build-output 构建应用程序

设置 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 所有执行都应在同一个 <plugin> 定义中,因此请确保你连接所有执行。

为了以带有 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 profile。

9. 设置覆盖率阈值

你可以使用 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 classDirectories 需要在 Gradle 的评估阶段之后读取
2 目前,Gradle JaCoCo 中存在一个错误,它要求以这种方式指定 excludes - https://github.com/gradle/gradle/issues/14760。一旦此问题得到修复,excludes
3 排除 org/example/package 包中的所有类

10. 结论

你现在拥有研究所测试覆盖率所需的所有信息!但是请记住,一些未覆盖的代码肯定没有经过良好的测试。但是一些覆盖的代码不一定是良好的测试。请务必编写好的测试!

相关内容