为什么 Jacoco 报告在连续测试运行中显示不同的覆盖率?

问题描述

我刚刚使用 jacoco-maven-pluginquarkus-junit5 向 Quarkus 应用程序添加了带有 maven-surefire-plugin 的 JaCoCo 代码覆盖率以进行单元测试。

当我运行 mvn clean verify 时,JaCoco 报告在那里,但我注意到当我再次运行它们时我得到了另一个覆盖。大多数情况下,覆盖率报告包括单元测试明确覆盖的行

我知道线路覆盖率实际上是 84%。 IntelliJ IDEA 覆盖率跑步者展示了这一点,而 JaCoCo 跑步者也有一次设法展示了这一点。

但大部分时间 JaCoCo 报告显示的线路覆盖率要低得多,为 20%30%

我在仪器离线模式下运行 JaCoCo,因为即时模式干扰了 Quarkus。

这是我的设置:

JaCoCo 代理依赖:

<dependency>
  <groupId>org.jacoco</groupId>
  <artifactId>org.jacoco.agent</artifactId>
  <classifier>runtime</classifier>
  <scope>test</scope>
  <version>${jacoco.version}</version>
</dependency>

JaCoCo 插件

<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>${jacoco.version}</version>
    <executions>
        <execution>
            <goals>
                <goal>instrument</goal>
                <goal>restore-instrumented-classes</goal>
                <goal>report</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Surefire 插件

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.0.0-M5</version>
    <configuration>
        <systemPropertyVariables>
            <jacoco-agent.destfile>${project.build.directory}/jacoco.exec</jacoco-agent.destfile>
            <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
            <maven.home>${maven.home}</maven.home>
            <hibernate.search.backend.directory.type>local-heap</hibernate.search.backend.directory.type>
        </systemPropertyVariables>
    </configuration>
</plugin>

如果你需要了解更多,pom.xml:

<?xml version="1.0"?>
    <project
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
        https://maven.apache.org/xsd/maven-4.0.0.xsd"
        xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>
    <artifactId>backend</artifactId>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

        <org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
        <jacoco.version>0.8.7</jacoco.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-resteasy</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-smallrye-health</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-junit5</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.exparity</groupId>
            <artifactId>hamcrest-date</artifactId>
            <version>2.0.7</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>rest-assured</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-container-image-docker</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-hibernate-validator</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-hibernate-orm-panache</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-flyway</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-resteasy-jackson</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-jdbc-postgresql</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-micrometer</artifactId>
        </dependency>
        <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-registry-prometheus</artifactId>
        </dependency>
        <dependency>
            <groupId>io.quarkus</groupId>
            <artifactId>quarkus-jdbc-h2</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.16</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.hibernate.search</groupId>
            <artifactId>hibernate-search-mapper-orm</artifactId>
        </dependency>
        <dependency>
            <groupId>org.hibernate.search</groupId>
            <artifactId>hibernate-search-backend-lucene</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>${org.mapstruct.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.jacoco</groupId>
            <artifactId>org.jacoco.agent</artifactId>
            <classifier>runtime</classifier>
            <scope>test</scope>
            <version>${jacoco.version}</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>${jacoco.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>instrument</goal>
                            <goal>restore-instrumented-classes</goal>
                            <goal>report</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.jboss.jandex</groupId>
                <artifactId>jandex-maven-plugin</artifactId>
                <version>1.0.8</version>
                <executions>
                    <execution>
                        <id>make-index</id>
                        <goals>
                            <goal>jandex</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <parameters>true</parameters>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M5</version>
                <configuration>
                    <systemPropertyVariables>
                        <jacoco-agent.destfile>${project.build.directory}/jacoco.exec</jacoco-agent.destfile>
                        <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
                        <maven.home>${maven.home}</maven.home>
                        <hibernate.search.backend.directory.type>local-heap</hibernate.search.backend.directory.type>
                    </systemPropertyVariables>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

如果有人知道这种行为的原因是什么并且可以帮助我,我将不胜感激。

如果您建议我使用 quarkus-jacoco 扩展名。我通过删除 jacoco-maven-plugin添加 quarkus-jacoco 扩展来尝试这个,但是在执行 mvn clean verify 后我遇到了另一个问题:

Caused by: io.quarkus.builder.BuildException:
Build failure: Build Failed due to errors
        [error]: Build step io.quarkus.jacoco.deployment.Jacocoprocessor#transformerBuildItem threw an exception: java.lang.NoClassDefFoundError: io/quarkus/deployment/builditem/BytecodeTransformerBuildItem$Builder
        at io.quarkus.jacoco.deployment.Jacocoprocessor.transformerBuildItem(Jacocoprocessor.java:71)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at io.quarkus.deployment.ExtensionLoader$2.execute(ExtensionLoader.java:972)
        at io.quarkus.builder.BuildContext.run(BuildContext.java:277)
        at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
        at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2046)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1578)
        at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1452)
        at java.base/java.lang.Thread.run(Thread.java:834)
        at org.jboss.threads.JBossthread.run(JBossthread.java:479)
Caused by: java.lang.classNotFoundException: io.quarkus.deployment.builditem.BytecodeTransformerBuildItem$Builder
        at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582)
        at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
        at java.base/java.lang.classLoader.loadClass(ClassLoader.java:521)
        at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:412)
        at io.quarkus.bootstrap.classloading.QuarkusClassLoader.loadClass(QuarkusClassLoader.java:365)
        ... 13 more

解决方法

最可能的原因(如果不仔细检查整个代码及其测试就真的无法判断)是您的大部分代码没有始终被测试覆盖。

例如,如果您的代码在午夜和凌晨 1 点之间运行时仅由测试执行,而另一半始终运行,则 jacoco 将在午夜半点报告 100% 的覆盖率,但在 4 点仅报告 50%下午。

这是一个人为的例子,但如果您的代码的执行路径取决于您在测试中没有嘲笑的外部因素,那么类似的事情可能会发生并且很常见。

举一个我自己遥远过去​​的主要例子,当且仅当特定的数据库表为空时,我在初级时编写的新模块的一个重要代码路径会导致崩溃。多年来,该特定表在开发、测试、验收或生产数据库中从未为空,因此在测试中从未注意到该问题,并且进行代码审查的人错过了它以及该表为空的想法从未发生过给他。当然,该模块的生产部署恰好与导致该表十年来第一次为空的数据转换相吻合,导致该模块在通电时立即崩溃。如果它在 10 分钟后通电(在第一次运行填充该表的批处理作业之后,我的只能从中读取)该错误将永远不会被注意到,但它受到了沉重打击。幸运的是,修复很简单,一个小时内补丁就出来了。模拟相关表并在其中运行不同模拟负载的单元测试本可以在一次又一次运行期间可靠地发现该错误,但这是 20 多年前在大型机系统上不存在的情况。