使用 JaCoCo 合并集成和单元测试报告

Merging Integration and Unit test reports with JaCoCo

我正在使用带有 JaCoCo 插件的 Maven 生成故障安全和万无一失的报告,但我只能设法将它们放在单独的报告中。我也想有一个整体覆盖视图(单元测试和集成测试之间的合并)。

经过我认为彻底的 google 搜索后,我只能找到一种使用 Sonar 执行此操作的方法。有没有更简单的方法来做到这一点?

相关问题:

您应该查看包含 merge 目标的 JaCoCo Maven 插件的文档。

不是您要找的答案,但仍然...

在大多数情况下,您不应合并单元测试和集成测试的覆盖范围。

单元测试的价值在于它们改进了应用程序的设计并确保代码的极端情况正常工作。您应该尝试对单元测试进行高分支覆盖。

集成测试的价值在于它们确保应用程序的主要用例正常工作并且整个堆栈正确集成。您应该尝试为您的集成测试提供高功能覆盖率。 (但用工具衡量功能覆盖率是相当困难的)。

如果您需要集成测试来提高分支覆盖率,这强烈表明您应该检查代码的设计。如果您在没有集成测试的情况下已经有很高的分支覆盖率,那么添加它们应该不会显着改变您的指标。

请随意对这个答案投反对票,因为它有点话题性,而且相当自以为是...

我最近实现了这个:经过一些麻烦和大量测试后,我有了一个运行良好的配置。

<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>${jacoco.version}</version>
    <executions>
        <execution>
            <id>pre-unit-test</id>
            <goals>
                <goal>prepare-agent</goal>
            </goals>
            <configuration>
                <destFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</destFile>
                <propertyName>surefireArgLine</propertyName>
            </configuration>
        </execution>
        <execution>
            <id>pre-integration-test</id>
            <goals>
                <goal>prepare-agent-integration</goal>
            </goals>
            <configuration>
                <destFile>${project.build.directory}/coverage-reports/jacoco-it.exec</destFile>
                <propertyName>testArgLine</propertyName>
            </configuration>
        </execution>
        <execution>
            <id>post-integration-test</id>
            <phase>post-integration-test</phase>
            <goals>
                <goal>report</goal>
            </goals>
            <configuration>
                <dataFile>${project.build.directory}/coverage-reports/jacoco-it.exec</dataFile>
                <outputDirectory>${project.reporting.outputDirectory}/jacoco-it</outputDirectory>
            </configuration>
        </execution>
        <execution>
            <id>post-unit-test</id>
            <phase>prepare-package</phase>
            <goals>
                <goal>report</goal>
            </goals>
            <configuration>
                <dataFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</dataFile>
                <outputDirectory>${project.reporting.outputDirectory}/jacoco-ut</outputDirectory>
            </configuration>
        </execution>
        <execution>
            <id>merge-results</id>
            <phase>verify</phase>
            <goals>
                <goal>merge</goal>
            </goals>
            <configuration>
                <fileSets>
                    <fileSet>
                        <directory>${project.build.directory}/coverage-reports</directory>
                        <includes>
                            <include>*.exec</include>
                        </includes>
                    </fileSet>
                </fileSets>
                <destFile>${project.build.directory}/coverage-reports/aggregate.exec</destFile>
            </configuration>
        </execution>
        <execution>
            <id>post-merge-report</id>
            <phase>verify</phase>
            <goals>
                <goal>report</goal>
            </goals>
            <configuration>
                <dataFile>${project.build.directory}/coverage-reports/aggregate.exec</dataFile>
                <outputDirectory>${project.reporting.outputDirectory}/jacoco-aggregate</outputDirectory>
            </configuration>
        </execution>
    </executions>
</plugin>
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.18.1</version>
    <configuration>
        <argLine>${surefireArgLine}</argLine>
        <skipTests>${skip.unit.tests}</skipTests>
        <includes>
            <include>**/*UT.java</include>
            <include>**/*MT.java</include>
            <include>**/*Test.java</include>
        </includes>
        <skipTests>${skipUTMTs}</skipTests>
    </configuration>
</plugin>
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <version>2.12.4</version>
    <configuration>
        <skipTests>${skipTests}</skipTests>
        <skipITs>${skipITs}</skipITs>
        <argLine>${testArgLine}</argLine>
        <excludes>
            <exclude>**/*UT*.java</exclude>
        </excludes>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>integration-test</goal>
                <goal>verify</goal>
            </goals>
        </execution>
    </executions>
</plugin>

如您所见,有 6 次不同的 Jacoco 执行 运行 测试、合并文件并创建汇总报告。在 Jacoco 配置之上,您还需要配置 Surefire 和 Failsafe 以从 Jacoco 获取参数(Surefire 运行s 单元测试和 Failsafe 运行s 集成测试)。

我使用的所有配置都应该在那里,你用它做的是你的设计架构,使其符合你想要的要求。就个人而言,如果您遇到文件未被读取的问题,我建议查看我在 surefire 和 failsafe 中排除和包含的内容。

基于乍得的回答,这是我的配置。它被配置为名为 jacoco 的单独配置文件,因此我可以轻松打开和关闭它。它仅使用默认配置。它可以为单元测试创​​建单独的代码覆盖率报告,为集成测试创建单独的代码覆盖率报告,也可以创建合并的代码覆盖率报告。

    <profile>
        <id>jacoco</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.jacoco</groupId>
                    <artifactId>jacoco-maven-plugin</artifactId>
                    <executions>
                        <execution>
                            <id>pre-unit-test</id>
                            <goals>
                                <goal>prepare-agent</goal>
                            </goals>
                        </execution>
                        <execution>
                            <id>post-unit-test</id>
                            <phase>test</phase>
                            <goals>
                                <goal>report</goal>
                            </goals>
                        </execution>
                        <execution>
                            <id>pre-integration-test</id>
                            <goals>
                                <goal>prepare-agent-integration</goal>
                            </goals>
                        </execution>
                        <execution>
                            <id>post-integration-test</id>
                            <goals>
                                <goal>report-integration</goal>
                            </goals>
                        </execution>
                        <execution>
                            <id>merge-results</id>
                            <phase>verify</phase>
                            <goals>
                                <goal>merge</goal>
                            </goals>
                            <configuration>
                                <fileSets>
                                    <fileSet>
                                        <directory>${project.build.directory}</directory>
                                        <includes>
                                            <include>*.exec</include>
                                        </includes>
                                        <excludes>
                                            <exclude>aggregate.exec</exclude>
                                        </excludes>
                                    </fileSet>
                                </fileSets>
                                <destFile>${project.build.directory}/aggregate.exec</destFile>
                            </configuration>
                        </execution>
                        <execution>
                            <id>post-merge-report</id>
                            <phase>verify</phase>
                            <goals>
                                <goal>report</goal>
                            </goals>
                            <configuration>
                                <dataFile>${project.build.directory}/aggregate.exec</dataFile>
                                <outputDirectory>${project.reporting.outputDirectory}/jacoco-aggregate</outputDirectory>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>

也许更好的方法是使用相同的 jacoco 文件,但让它进行另一个测试,这对我们有用 - 单元和它测试。

            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>0.8.1</version>
                <executions>
                    <execution>
                        <id>default-prepare-agent</id>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                        <configuration>
                            <skip>${maven.surefire.skipTests}</skip>
                            <propertyName>maven.surefire.argLine</propertyName>
                            <!-- using the same dest file for both UT and IT -->
                            <destFile>${sonar.jacoco.reportPath}</destFile>
                        </configuration>
                    </execution>
                    <execution>
                        <id>default-prepare-agent-integration</id>
                        <goals>
                            <goal>prepare-agent-integration</goal>
                        </goals>
                        <configuration>
                            <skip>${maven.failsafe.skipTests}</skip>
                            <propertyName>maven.failsafe.argLine</propertyName>
                            <!-- append to the UT dest file -->
                            <destFile>${sonar.jacoco.reportPath}</destFile>
                            <append>true</append>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
            <artifactId>maven-surefire-plugin</artifactId>
            <configuration>
                <skipTests>${maven.surefire.skipTests}</skipTests>
                <failIfNoTests>${maven.surefire.failIfNoTests}</failIfNoTests>
                <!-- allow argLine to be modified by other plugins, e.g. jacoco -->
                <argLine>@{maven.surefire.argLine}</argLine>
            </configuration>
        </plugin>
        <plugin>
            <artifactId>maven-failsafe-plugin</artifactId>
            <configuration>
                <skipTests>${maven.failsafe.skipTests}</skipTests>
                <failIfNoTests>${maven.failsafe.failIfNoTests}</failIfNoTests>
                <!-- allow argLine to be modified by other plugins, e.g. jacoco -->
                <argLine>@{maven.failsafe.argLine}</argLine>
            </configuration>
        </plugin>

我明白单元测试确实应该是代码覆盖率的源头……但有时,人们希望有能力知道,对吗?这是我所做的(这是使用 gradle)。

plugins {
    [...]
    id 'jacoco'
}

[...]

test {
    jacoco { /* specify the "exec" data file name (see the "Unit" in there) */
        destinationFile = file("$buildDir/jacoco/jacocoTestUnit.exec")
        classDumpDir = file("$buildDir/jacoco/classpathdumpsUnit")
    }
    useJUnitPlatform { /* I use the JUnit Jupiter @Tag Annotation to create my suites... you are free to do something else */
        excludeTags 'IT'
    }
    description = "Run unit tests"
    group = "verification"
}

task intTest(type: Test) {
    jacoco {
        destinationFile = file("$buildDir/jacoco/jacocoTestInt.exec")
        classDumpDir = file("$buildDir/jacoco/classpathdumpsInt")
    }
    useJUnitPlatform {
        includeTags 'IT'
    }
    description = "Run integration tests"
    group = "verification"
}

jacocoTestReport {
    /*
     * find all test exec files and feed them to jacoco
     */
    def list = []
    def dir = new File("${buildDir}/jacoco/")
    if(dir.exists()) {
        dir.eachFileRecurse(FileType.FILES) { file ->
            /* gather all the "exec" files available */
            if (file.getName().startsWith("jacocoTest") && file.getName().endsWith(".exec")) {
                list << file.getAbsolutePath()
            }
        }
        /* provide all the "exec" files to jacoco */
        executionData.from = files(list)
    }

    /* 
     * you must run all tests before running jacoco.
     * We want the liberty to run partial tests instead of all,
     * so this task doesn't depend on any test task.
     */
    reports {
        xml.enabled true
    }
}

这样,您就可以通过以下方式从单元测试中获得覆盖率:

./gradlew clean test jacocoTestReport

或者您可以通过以下方式获得集成测试的覆盖率:

./gradlew clean intTest jacocoTestReport

或者您可以通过以下方式全面覆盖单元测试和集成测试:

./gradlew clean test inTest jacocoTestReport

免责声明:我不是 Jacoco 或 Gradle 专家...请随时评论我可能忘记的任何内容。到目前为止,这非常适合我的需要。

为了merge这里的报告,我有一个完整的工作解决方案。

请注意,为了使 merge 策略正常工作,阶段应按顺序执行(如果 mvn testmvn verify -DskipUnitTests 将并行执行,则有可能不能正常工作)。

<!-- Jacoco is used to generate the reports for SonarQube -->
<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.8.6</version>

    <configuration>
        <skip>${skipTests}</skip>
    </configuration>

    <executions>
        <!-- Prepares the property pointing to the JaCoCo runtime agent which
             is passed as VM argument when Maven the Surefire plugin is executed. -->
        <execution>
            <id>pre-unit-test</id>
            <goals>
                <goal>prepare-agent</goal>
            </goals>
            <configuration>
                <!-- Sets the path to the file which contains the execution data. -->
                <destFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</destFile>
                
                <!-- Sets the name of the property containing the settings for JaCoCo runtime agent. -->
                <propertyName>surefireArgLine</propertyName>
            </configuration>
        </execution>
        
        <!-- Make sure that after the Unit Tests execution, the jacoco-ut.exec file is generated,
             will be merged to the aggregation file -->
        <execution>
            <id>post-unit-merge</id>
            <phase>test</phase>
            <goals>
                <goal>merge</goal>
            </goals>
            <configuration>
                <fileSets>
                    <fileSet>
                        <directory>${project.build.directory}</directory>
                        <includes>
                            <include>**/coverage-reports/*.exec</include>
                        </includes>
                    </fileSet>
                </fileSets>
                <destFile>${project.build.directory}/coverage-reports/jacoco_aggregation.exec</destFile>
            </configuration>
        </execution>
        
        <!-- Ensures that the code coverage report is created/updated after unit tests have been run. -->
        <execution>
            <id>post-unit-test</id>
            <phase>test</phase>
            <goals>
                <goal>report</goal>
            </goals>
            <configuration>
                <!-- Sets the path to the file which contains the execution data. -->
                <dataFile>${project.build.directory}/coverage-reports/jacoco_aggregation.exec</dataFile>
                
                <!-- Sets the output directory for the code coverage report. -->
                <outputDirectory>${project.reporting.outputDirectory}/jacoco</outputDirectory>
            </configuration>
        </execution>

        <!-- Prepares the property pointing to the JaCoCo runtime agent which
            is passed as VM argument when Maven the Failsafe plugin is executed. -->
        <execution>
            <id>pre-integration-test</id>
            <phase>pre-integration-test</phase>
            <goals>
                <goal>prepare-agent</goal>
            </goals>
            <configuration>
                <!-- Sets the path to the file which contains the execution data. -->
                <destFile>${project.build.directory}/coverage-reports/jacoco-it.exec</destFile>
                
                <!-- Sets the name of the property containing the settings for JaCoCo runtime agent. -->
                <propertyName>failsafeArgLine</propertyName>
            </configuration>
        </execution>
        
        <!-- Make sure that after the Integration Test execution, the jacoco-it.exec file is generated,
             will be merged to the aggregation file -->
        <execution>
            <id>post-integration-merge</id>
            <phase>post-integration-test</phase>
            <goals>
                <goal>merge</goal>
            </goals>
            <configuration>
                <fileSets>
                    <fileSet>
                        <directory>${project.build.directory}</directory>
                        <includes>
                            <include>**/coverage-reports/*.exec</include>
                        </includes>
                    </fileSet>
                </fileSets>
                <destFile>${project.build.directory}/coverage-reports/jacoco_aggregation.exec</destFile>
            </configuration>
        </execution>
        
        <!-- Ensures that the code coverage report is created/updated after integration tests have been run. -->
        <execution>
            <id>post-integration-test</id>
            <phase>post-integration-test</phase>
            <goals>
                <goal>report</goal>
            </goals>
            <configuration>
                <!-- Sets the path to the file which contains the execution data. -->
                <dataFile>${project.build.directory}/coverage-reports/jacoco_aggregation.exec</dataFile>
                
                <!-- Sets the output directory for the code coverage report. -->
                <outputDirectory>${project.reporting.outputDirectory}/jacoco</outputDirectory>
            </configuration>
        </execution>
    </executions>
</plugin>

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <!-- Sets the VM argument line used when unit tests are run. -->
        <argLine>${surefireArgLine}</argLine>
    </configuration>
</plugin>

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-failsafe-plugin</artifactId>
    <configuration>
        <!-- Sets the VM argument line used when integration tests are run. -->
        <argLine>${failsafeArgLine}</argLine>
    </configuration>
</plugin>

现在,由于报告已生成,可以执行声纳命令以发布报告:

mvn sonar:sonar -Dsonar.coverage.jacoco.xmlReportPaths="target/site/jacoco/jacoco.xml"

这是开箱即用的。

解释

prepare-agent 目标默认与 SureFire 和 FailSafe 插件集成(确切地说,prepare-agent 设置的 argLine 参数被两个插件使用)。此外,prepare-agent 默认情况下配置 Jacoco,以便它在文件已经存在的情况下附加覆盖率结果,所以最后你会得到一个 target/jacoco.exec 文件,其中包含单元测试和集成测试的结果。

以防万一这是相关的配置,因为您看到默认情况下没有任何变化:):

    <plugins>
      <plugin>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.22.2</version>
        <executions>
          <execution>
            <goals>
              <goal>test</goal>
            </goals>
          </execution>
        </executions>
      </plugin>


      <plugin>
        <artifactId>maven-failsafe-plugin</artifactId>
        <version>2.22.2</version>
        <executions>
          <execution>
            <goals>
              <goal>integration-test</goal>
              <goal>verify</goal>
            </goals>
          </execution>
        </executions>
      </plugin>


      <plugin>
        <groupId>org.jacoco</groupId>
        <artifactId>jacoco-maven-plugin</artifactId>
        <version>0.8.6</version>
        <executions>
          <execution>
            <goals>
              <goal>prepare-agent</goal>
              <goal>report</goal>
              <goal>check</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>