概述

Java应用代码的单元测试覆盖率工具Jacoco已经非常成熟及完善了。对于历史的Java项目很多都是没有单元测试的,往往在新的在迭代的过程中都希望能有变更代码行的单元测试覆盖率,从而提高迭代质量。

本文介绍怎么基于jacoco和maven构建变更行单元测试覆盖率报告。对于全量单测覆盖率报告怎么使用可以参考Jacoco官方文档

获取变更行

要构建变更行测试覆盖率,那么首先我们要定位出源代码的变更文件和对应变更行号。

  1. 获取变更文件列表
git diff --name-status  ${baseCommintId} ${currentCommintId}

代码测试覆盖率 java java单元测试代码覆盖率_java

解析以上命令行结果可以获得变更文件列表
3. 获取文件变更行明细和行号

git diff -U100000 --cc -W  ${baseCommintId} ${currentCommintId} ${filePath}

代码测试覆盖率 java java单元测试代码覆盖率_代码测试覆盖率 java_02

解析返回结果,能够得到对应文件的变更行行号。

生成变更行覆盖率报告

代码测试覆盖率 java java单元测试代码覆盖率_增量代码覆盖率_03

JaCoCo 的注入逻辑用的是 ASM 库,都是基于JVM指令注入,比较复杂。我们用了一个比较快速简单的方式:前面生成全量覆盖率数据的流程不变,只对解析exec 文件生成报告做改造,过滤掉非变更行的行号,生成我们所需要的增量覆盖率模型。

最佳实践

对于现在的应用,大部分都是基于Maven的多模块应用。多模块配置官方推荐。

  1. 应用多模块

多模块示例说明

• demo-api 是应用的基础api
• demo-service 是应用的核心业务处理模块,依赖api
• demo-app 是应用的最终运行,依赖service
• demo-build是独立模块,用于生成report
  1. root应用的pom.xml配置

在根pom里面设置jacoco的agent插装及单模块的测试覆盖率报告

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.example</groupId>
    <artifactId>jacoco_demo</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>demo-service</module>
        <module>demo-build</module>
        <module>demo-api</module>
        <module>demo-app</module>
    </modules>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <jacoco.version>0.8.6-diff-20210324</jacoco.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>${jacoco.version}</version>
                <configuration>
                    <includes>
                        <include>com/demo/**/*</include>
                    </includes>
                </configuration>
                <executions>
                    <execution>
                        <id>prepare-agent</id>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>report</id>
                        <phase>test</phase>
                        <goals>
                            <goal>report</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
  1. 各子模块配置

子模块会继承root模块的配置,无需特殊配置

  1. build模块配置

build模块需要聚合各个子模块的测试报告,切生成变更行的覆盖率报告
build模块需要依赖所有的子模块
build模块需要配置git的基础commitId: mvn.git.base.commit

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>jacoco_demo</artifactId>
        <groupId>org.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>demo-build</artifactId>

    <properties>
        <mvn.git.base.commit>36a2****338a</mvn.git.base.commit>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>demo-service</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>demo-app</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.example</groupId>
            <artifactId>demo-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>${jacoco.version}</version>
                <executions>
                    <-- 各模块全量覆盖率聚合报告 -->
                    <execution>
                        <id>report-aggregate</id>
                        <phase>test</phase>
                        <goals>
                            <goal>report-aggregate</goal>
                        </goals>
                    </execution>
                    
                    <-- 各模块增量覆盖率聚合报告 -->
                    <execution>
                        <id>report-aggregate-diff</id>
                        <phase>test</phase>
                        <goals>
                            <goal>report-aggregate-diff</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
  1. 构建及单元测试

在根目录构建单元测试,各个子模块会生成各自模块的全量单元测试覆盖率
build模块会生成全量模块的聚合单元测试覆盖率,且会生成增量的覆盖率

代码测试覆盖率 java java单元测试代码覆盖率_增量代码覆盖率_04

jacoco-aggregate-diff/index.html 只看行覆盖率,其他的列忽略
jacoco-aggragate/index.html 全量代码覆盖率

代码测试覆盖率 java java单元测试代码覆盖率_jacoco_05

后记

基于git和jaccoco的maven插件,我们能获取代码变更行的单元测试覆盖率。对于历史悠久且无单元测试的Java应用迭代可以尽量的控制增量代码单测的规范要求。