代码覆盖率
1.介绍
代码覆盖率是软件测试中的一个度量指标,它描述了在进行测试时程序源代码被实际执行和检查的比例和程度。这个比例通常以百分比形式表示,用于评估测试的质量和完整性
一般项目会要求类和方法的覆盖率达到100%,行和分支的覆盖率到80%。其实覆盖率的主要指标应该是分支覆盖率,了解分支覆盖率可帮助我们进一步完善测试用例
2.类型
- 行覆盖率:衡量源代码中被执行的行数占总行数的比例
- 分支覆盖率:关注代码中的条件语句(如if/else、switch),测量所有可能的分支路径中有多少被执行
- 指令覆盖率:在更低的字节码或机器码级别上,测量已执行的指令数量占总指令数量的比例
- 方法覆盖率:计算被测试调用的方法数量占总方法数量的比例
- 类覆盖率:测量被测试的类数量占总类数量的比例
- 圈复杂度覆盖率(或称McCabe复杂度覆盖率):反映代码的结构复杂性,计算需要测试的独立路径数量
3.idea实现JUnit代码测试覆盖率
注意
被测试代码
JUnit代码
查看某个单元测试的覆盖率报告
查看所有某个类的所有单元测试的覆盖率报告
4.Jacoco
介绍
JaCoCo是一个流行的Java代码覆盖率库。保代码的各个部分都得到了测试它用于测量代码的执行覆盖率,通常与单元测试一起使用,以确保代码的各个部分都得到了测试
核心原理
- On-the-fly插桩:JaCoCo使用On-the-fly模式进行代码覆盖率的收集。在这种模式下JaCoCo通过Java代理(JaCoCo Aqent)在运行时动态地修改字节码。当Java虚拟机(JVM)加载类文件时,代理会拦截这个过程,并在需要的地方插入覆盖检测代码。这些插入的代码用于跟踪方法调用、分支执行等,以便计算覆盖率
- ASM库的使用:JaCoCo依赖于ASM库,这是一个Java字节码操作和分析框架。ASM允许JaCoCo在运行时解析和修改Java字节码。通过ASM,JaCoCo可以在不改变原有源代码的情况下,将覆盖率检测逻辑注入到目标类的字节码中
效果
github地址
下载最新的Jacoco Agent与CLI
使用步骤
- 下载最新的Jacoco Agent与CLI
- 把:Jacoco Agent与CLI,程序的jar包,程序源代码(也要有编译后的字节码)目录 这些放在一个目录
- 启动jar包,当然命令比较特殊
- 通过代码访问已经运行jar包的Controller(运行测试用例)
- 运行Jacocoli的jar包,访问端口dump下来生成运行时文件
- 通过命令把运行时文件生成报告
- 查看报告
- 注意:如果重新运行了测试用例需要重新生成运行时文件和重新生成报告
相关命令
# 特殊的启动jar包
# java: 这是执行Java应用程序的命令
# -javaagent:jacocoagent.jar: 这个参数指定要使用的Java代理(agent),在这个案例中是JaCoCo的代理jar文件
# includes=*: 这个参数配置JaCoCo应该包括所有包下的类进行代码覆盖率的收集
# output=tcpserver: 这个参数设置JaCoCo的输出模式为TCP服务器,这意味着JaCoCo会启动一个TCP服务器来接收和处理覆盖率数据
# port=6300: 这个参数指定了TCP服务器监听的端口号为6300
# address=localhost: 这个参数设置了TCP服务器绑定的地址为本地主机(localhost)
# append=true: 这个参数表示如果覆盖率数据文件已经存在,新的数据应追加到原有文件中,而不是覆盖它
# -jar target-application.jar: 最后这部分指定了要运行的Java应用程序jar文件
java -javaagent:jacocoagent.jar=includes=*,output=tcpserver,port=6300,address=localhost,append=true -jar target-application.jar
<!---->
# 这个命令是用来从已经在本地主机(127.0.0.1)上运行并配置为通过TCP服务器在端口6300上输出覆盖率数据的Java应用程序中,抓取并保存代码覆盖率数据到名为target-application-coverage.exec的文件中。这些数据后续可以被JaCoCo或者其他工具用来生成详细的代码覆盖率报告。
java -jar jacococli.jar dump --address 127.0.0.1 --port 6300 --destfile target-application-coverage.exec
<!---->
# 这个命令是用来根据之前收集的target-application-coverage.exec覆盖率数据文件,以及相关的类文件和源代码文件,生成HTML和XML格式的代码覆盖率报告。生成的报告将展示哪些代码行被执行,未被执行,以及执行的比例等信息,有助于分析和理解应用程序的测试覆盖情况
java -jar jacococli.jar report target-application-coverage.exec --classfiles .\jacoco-coverage\target-application\target\classes --sourcefiles .\jacoco-coverage\target-application\src\main\java --html html-report --xml report.xml --encoding=utf-
5.100%覆盖率是骗人的
介绍
覆盖率只是一个量化指标,它告诉我们那些代码被执行了,但绝不代表那些代码是正确的
盲目追求100%的覆盖率,不仅会导致边际效益递减,还可能催生出大量为了跑通而写的虚假测试
代码覆盖率通常指的是行覆盖率,即测试运行期间,有多少行代码被执行过。但执行过不等于测试过,更不等于逻辑正确
问题
- 虚假测试
- 边际效益递减
虚假测试
案例1
被测试方法
public class StringUtil {
public String getPrefix(String input) {
return input.substring(0,5);
}
}
测试代码
@Test
public void testGetPrefix() {
StringUtil util = new StringUtil();
String result = util.getPrefix("HelloWorld");
assertEquals("Hello",result);
}
解释
覆盖率达到了100%,但是没有测试传递null或者传递字符串小于5的情况,这都会导致异常,覆盖率无法检测数据维度的缺陷
案例2
被测试方法
public doubale calculateDiscount(int price) {
double finalPrice = price;
if(price > 100){
finalPrice = price * 0.9;
}
return finalPrice;
}
测试代码
@Test
public void testDiscount() {
assertEquals(180.0,calculateDiscount(200),0.01);
}
解释
覆盖率达到了100%,但是当传递参数为100以下或等于100的情况都没有测试到
案例3
测试代码
@Test
public void testDiscount() {
OrderService service = new OrderService();
service.processOrder(new Order());
// 没有断言
}
解释
这种测试被称为Assertion-Free Testing。它除了证明代码运行不报错之外,没有任何业务价值,纯粹是为了骗过SonarQube等扫描工具
边际效益递减
- 测试琐碎代码:为了100%覆盖度,你要测试一些没有垃圾代码,比如get,set这些代码没有任何测试意义
- 测试脆弱性增加:为了覆盖某些极端的异常捕获,你可能需要使用复杂的Mock框架去模拟及其罕见的底层错误。一旦代码重构,这些脆弱的测试就会报错,增加维护成本
- 开发效率降低:开发者花费大量时间编写低价值的测试,而不是优化业务逻辑