精准测试 & Jacoco 代码覆盖率统计实战
目录
精准测试介绍
测试现状
测试设计阶段
-
测试范围
- 评估少:质量差
- 评估多:成本高
-
测试设计
- 设计全:成本高
- 设计少:用时少、风险高
交付测试阶段
- 开发工程师
- 顺手改了代码,忘记同步,对上线有什么影响?
- 马上要上线了,系统达到质量标准了么?
- 测试工程师
- 核心功能是否回归足够?
- 功能点这么多,测完还会有遗漏测试点么?
- 代码变动太频繁,上线会有什么风险么?
于是引出想解决的问题
- 这次版本新增/修改的代码,都有测试覆盖到吗?
- 上线前的回归,怎样弄清楚测试范围?
什么是精准测试?
精准测试的目标
- 降低测试成本:根据代码变更定位测试范围
- 提高测试覆盖率:更准确覆盖被测业务
- 提高测试用例有效性:反推有效测试用例
精准测试的应用
- 测试用例
- 利用线上数据反推有效测试用例
- 利用灰度公测
- 语法树
- 根据研发代码的变更,筛选精准测试用例
- 利用代码和调用链,来反向完善测试用例
Jacoco 代码覆盖率统计实战
Jacoco 简介
什么是 Jacoco ?
- Jacoco 是一个免费的 Java 代码覆盖率统计工具,它是由 EclEmma 团队根据多年来使用和集成现有库的经验教训创建的。
常见的 Java 代码覆盖率统计工具:
- Jacoco
- Cobertura(维护较少)
- Emma(基于 Jacoco)
为什么选择 Jacoco ?
官方解释:
有几种可用的 Java 开源覆盖技术在实现 Eclipse 插件 EclEmma 时,观察到它们都不是真正为集成而设计的。它们中的大多数专门适用于特定工具(Ant 任务、命令行、IDE 插件),并且不提供允许嵌入不同上下文的文档化 API。两个最好的和广泛使用的开源工具是 EMMA 和 Cobertura。这两个工具都不再由原作者积极维护,也不支持当前的 Java 版本。由于缺乏回归测试,维护和功能添加很困难。
Jacoco 特点:
- 覆盖率分析指标丰富
- 可以不依赖源代码
- 集成方式灵活
- 开发框架无关
- 兼容所有的java 版本
- java kotlin scala 等多种 jvm 语言支持
- 多种报告格式
- 允许远程控制
- 有独立的工具与 ant maven 的集成支持
Jacoco 支持的覆盖率指标
- 指令覆盖率 Instructions
- 分支覆盖率 Branches
- 圈复杂度覆盖率 Cyclomatic ComplexityV(G) = e-n+2p
- 代码行覆盖率 Lines
- 方法覆盖率 Methods
- 类覆盖率 Classes
Jacoco 不支持的覆盖率指标
- 条件覆盖率(Conditioncoverage):每一个逻辑表达式中的每一个条件(无法再分解的逻辑表达式)是否都有运行到成立及不成立的情形。
- 条件/判断覆盖率(Condition/decisioncoverage):需同时满足判断覆盖率和条件覆盖率。
- 修改条件/判断覆盖(modifed condition/decision coverage):简称 MC/DC,即在影响判断结果的条件中,每个变量都出现至少二次,其中至少一次其值为真,至少一次其值为假。
- 循环覆盖率(Loopcoverage):所有循环是否都有运行过零次、一次及一次以上的测试。
- 参数值覆盖率(Parameter Value Coverage):对于一个方法的所有参数,是否有运行过其中最常见的数值?
代码插桩技术 ASM
字节码
什么是字节码?
可以使用 JDK 自带的javap
工具来查看字节码相关信息:
C:\Users\JUNOLU\IdeaProjects\Demo\target\classes>javap --help
用法: javap <options> <classes>
其中, 可能的选项包括:
-help --help -? 输出此用法消息
-version 版本信息
-v -verbose 输出附加信息
-l 输出行号和本地变量表
-public 仅显示公共类和成员
-protected 显示受保护的/公共类和成员
-package 显示程序包/受保护的/公共类
和成员 (默认)
-p -private 显示所有类和成员
-c 对代码进行反汇编
-s 输出内部类型签名
-sysinfo 显示正在处理的类的
系统信息 (路径, 大小, 日期, MD5 散列)
-constants 显示最终常量
-classpath <path> 指定查找用户类文件的位置
-cp <path> 指定查找用户类文件的位置
-bootclasspath <path> 覆盖引导类文件的位置
C:\Users\JUNOLU\IdeaProjects\Demo\target\classes>
C:\Users\JUNOLU\IdeaProjects\Demo\target\classes>javap Demo.class
Compiled from "Demo.java"
public class Demo {
public Demo();
public static void main(java.lang.String[]);
}
字节码变更
字节码操作常用框架
- cglib
- javassist
- bytebuddy
- byteman
- jvm-sandbox
- asm(jacoco 的底层实现)
插桩原理
- 对 JVM 的字节码插桩
- 基于代码块(block)插桩(如图中的探针 P)
- 计算所覆盖的代码块
插桩方式
推荐方式:Java Agent
On-The-Fly 即时插桩模式
- 修改 jvm 启动参数加入 java agent。
- 加载 java agent 的逻辑运行在 java 的主方法之前。
- java agent 在类加载期间修改类并进行插桩。
- java 主方法运行时,所有类已经被插桩,运行后生成覆盖率数据。
Offline 离线插桩模式
在测试前先对文件进行插桩,然后生成插过桩的 class 或 jar 包。在测试插过桩的 class 和 jar 包后,会生成动态覆盖信息到文件,最后统一对覆盖信息进行处理,并生成报告。
- 不支持 java agent
- 不支持配置 jvm 参数
- 字节码不兼容的平台
- 与其他 agent 冲突
两种插桩方式对比
- On-the-fly 无需提前进行字节码插桩。
- On-the-fly 无需停机(Offline需要停机),可以实时获取覆盖率。
- On-the-fly 能更加方便获取代码覆盖率,但是其代理服务会对被测应用造成一定的性能损耗。
jacoco 实战
演练环境
- jacoco 最新版本下载
- java 17
- 被测应用:git clone https://github.com/spring-io/start.spring.io.git
- 配置环境变量:
- $JACOCO_HOME:E:\precise_test\jacoco-0.8.9
- $TARGET_HOME:E:\precise_test\start.spring.io\start-site
jacoco 使用
1)加入插桩
启动被测应用,并设置覆盖率统计方式为 tcp 模式:
java -javaagent:$JACOCO_HOME//lib/jacocoagent.jar=output=tcpserver \
-jar $TARGET_HOME/target/start-site-exec.jar
进行页面操作:
2)dump:覆盖率数据导出
覆盖率数据文件名示例:testcase.exec
- 清空(之前的)并生成最新的覆盖率数据:
rm testcase.exec
java -jar $JACOCO_HOME/lib/jacococli.jar \
dump \
--address 127.0.0.1 --port 6300 \
--reset \
--destfile testcase.exec
- 不清空(之前的)并生成最新的覆盖率数据:
java -jar $JACOCO_HOME/lib/jacococli.jar \
dump \
--address 127.0.0.1 --port 6300 \
--destfile testcase.exec
3)report:生成覆盖率报告
生成覆盖率报告并关联源码:
java -jar $JACOCO_HOME/lib/jacococli.jar \
report testcase.exec \
--html jacoco_report \
--xml jacoco.xml \
--csv jacoco.csv \
--classfiles $TARGET_HOME/target/classes/ \
--sourcefiles $TARGET_HOME/src/main/java/
打开报告目录中的 index.html,查看覆盖率情况:
- 绿条:表示已覆盖的代码行。
- 黄条:表示其下分支有未被覆盖的代码行。
- 红条:表示未被覆盖的代码行。