08.测试覆盖率

0 阅读6分钟

代码覆盖率

1.介绍

代码覆盖率是软件测试中的一个度量指标,它描述了在进行测试时程序源代码被实际执行和检查的比例和程度。这个比例通常以百分比形式表示,用于评估测试的质量和完整性

一般项目会要求类和方法的覆盖率达到100%,行和分支的覆盖率到80%。其实覆盖率的主要指标应该是分支覆盖率,了解分支覆盖率可帮助我们进一步完善测试用例

2.类型

  • 行覆盖率:衡量源代码中被执行的行数占总行数的比例
  • 分支覆盖率:关注代码中的条件语句(如if/else、switch),测量所有可能的分支路径中有多少被执行
  • 指令覆盖率:在更低的字节码或机器码级别上,测量已执行的指令数量占总指令数量的比例
  • 方法覆盖率:计算被测试调用的方法数量占总方法数量的比例
  • 类覆盖率:测量被测试的类数量占总类数量的比例
  • 圈复杂度覆盖率(或称McCabe复杂度覆盖率):反映代码的结构复杂性,计算需要测试的独立路径数量

3.idea实现JUnit代码测试覆盖率

注意

33341022231339.png

被测试代码

1729609497490.png

JUnit代码

1729609536275.png

查看某个单元测试的覆盖率报告

1729609592252.png

1729609652659.png

11111111111111111230847.png

查看所有某个类的所有单元测试的覆盖率报告

1729609825668.png

qqq022231155.png

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可以在不改变原有源代码的情况下,将覆盖率检测逻辑注入到目标类的字节码中

效果

0e0f7f59f71a4555b263620c14a72c69.png

github地址

下载最新的Jacoco Agent与CLI

github.com/jacoco/jaco…

使用步骤

  1. 下载最新的Jacoco Agent与CLI
  2. 把:Jacoco Agent与CLI,程序的jar包,程序源代码(也要有编译后的字节码)目录 这些放在一个目录
  3. 启动jar包,当然命令比较特殊
  4. 通过代码访问已经运行jar包的Controller(运行测试用例)
  5. 运行Jacocoli的jar包,访问端口dump下来生成运行时文件
  6. 通过命令把运行时文件生成报告
  7. 查看报告
  8. 注意:如果重新运行了测试用例需要重新生成运行时文件和重新生成报告

相关命令

    # 特殊的启动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框架去模拟及其罕见的底层错误。一旦代码重构,这些脆弱的测试就会报错,增加维护成本
  • 开发效率降低:开发者花费大量时间编写低价值的测试,而不是优化业务逻辑

建议的覆盖度指标

1774115653077.png