你们单测覆盖率是如何统计的?原理是什么?

高手回答

我们在进行单元测试时,经常需要关注一个覆盖率的指标,许多发布流程甚至要求达到特定的百分比。

那么,单元测试覆盖率是如何统计的呢?其底层实现原理又是怎样的呢?

单元测试覆盖率的统计原理实际上是通过字节码插桩实现的。也就是说,在编译期间会向代码中注入一些特殊的监控代码,以记录测试执行过程中代码的执行情况,从而推断代码的覆盖情况。这些监控代码能在运行时记录代码的执行情况,也能在编译时生成代码覆盖率报告。

常见的单元测试覆盖率统计工具包括JaCoCoEmmaCobertura等,这些工具能够在编译或运行时对代码进行插桩,并记录代码的执行情况,最终生成覆盖率报告。

具体见下表:

工具JacocoEmmaCobertura
原理使用 ASM 修改字节码修改 jar 文件,class 文件字节码文件基于 jcoverage,基于 asm 框架对 class 文件插桩
覆盖粒度行,类,方法,指令,分支行,类,方法,基本块,指令,无分支覆盖项目,包,类,方法的语句覆盖/分支覆盖
插桩on the fly、offlineon the fly、offlineoffline,把统计代码插入编译好的class文件中
生成结果在 Tomcat 的 catalina.sh 配置 javaangent 参数,指出需要收集覆盖率的文件,shutdown 时才收集,只能使用 kill 命令关闭 Tomcat,不要使用 kill -9html、xml、txt,二进制格式报表html,xml
缺点需要源代码1、需要 debug 版本,并打来 build.xml 中的 debug 编译项; 2、需要源代码,且必须与插桩的代码完全一致1、不能捕获测试用例中未考虑的异常; 2、关闭服务器才能输出覆盖率信息(已有修改源代码的解决方案,定时输出结果;输出结果之前设置了 hook,会与某些服务器的 hook 冲突,web 测试中需要将 cobertura.ser 文件来回 copy
性能小巧插入的字节码信息更多
执行方式maven,ant,命令行命令行maven,ant
Jenkins 集成生成 html 报告,直接与 hudson 集成,展示报告,无趋势图无法与 hudson 集成有集成的插件,美观的报告,有趋势图
报告实时性默认关闭,可以动态从 jvm dump 出数据可以不关闭服务器默认是在关闭服务器时才写结果
维护状态持续更新中停止维护停止维护,不支持java1.8的lamda表达式
什么是字节码插桩

Java字节码插桩技术是指在编译期或运行期,通过修改Java字节码的方式,在代码中插入额外的代码。这种技术可以在不改变Java源代码的情况下,对Java应用程序的运行时行为进行监控、调试、分析和优化等操作。举例来说,它可以用于实现性能监控、代码覆盖率检测、代码安全扫描等功能。

字节码插桩技术通常包括以下几个步骤:

  1. 生成目标类的字节码,这一步可以通过Java编译器(如javac)或其他工具(如AspectJ)来完成。
  2. 解析字节码,识别需要进行插桩的代码区域(如方法、循环、异常处理等)。
  3. 插入额外的字节码,通常通过编写Java代码来实现这一步,然后利用字节码生成库(如ASM、Javassist等)生成相应的字节码。
  4. 将修改后的字节码重新写回到磁盘或内存中,以供后续使用。

假设我们希望对一个Java方法进行性能监控,我们可以在方法的入口和出口处分别插入计时器,以统计方法的执行时间。以下代码展示了如何实现这一功能:

  1. public class Monitor {

  2.     public static void start() {

  3.         long startTime = System.nanoTime();

  4.         // 将起始时间记录到ThreadLocal中,以便在方法返回时进行计算

  5.         ThreadLocalHolder.set("startTime", startTime);

  6.     }

  7.     public static void end() {

  8.         long endTime = System.nanoTime();

  9.         // 获取起始时间

  10.         long startTime = (long) ThreadLocalHolder.get("startTime");

  11.         // 计算方法执行时间

  12.         long elapsedTime = endTime - startTime;

  13.         System.out.println("Method execution time: " + elapsedTime + "ns");

  14.     }

  15. }

  16. public class Example {

  17.     public void method() {

  18.         Monitor.start();

  19.         // 执行方法逻辑

  20.         Monitor.end();

  21.     }

  22. }

然而,若需监控多个方法的性能,分别在每个方法中插入Monitor.start()和Monitor.end()将导致代码重复、可读性下降,并存在遗漏的风险。在这种情况下,可以借助字节码插桩技术,在编译期或运行期间自动向每个方法的入口和出口处插入Monitor.start()和Monitor.end(),以确保代码的统一性和可维护性。

具体实现可借助字节码生成库ASM或Javassist来实现,此处以ASM为例。以下代码展示了如何使用ASM对Example类进行字节码插桩:

  1. import org.objectweb.asm.ClassReader;

  2. import org.objectweb.asm.ClassVisitor;

  3. import org.objectweb.asm.ClassWriter;

  4. import org.objectweb.asm.MethodVisitor;

  5. import org.objectweb.asm.Opcodes;

  6. import java.io.IOException;

  7. public class MonitorTransformer implements Opcodes {

  8.     public static byte[] transform(byte[] classBytes) throws IOException {

  9.         ClassReader reader = new ClassReader(classBytes);

  10.         ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);

  11.         ClassVisitor visitor = new ClassVisitor(Opcodes.ASM5, writer) {

  12.             @Override

  13.             public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {

  14.                 MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);

  15.                 // 只为指定方法添加字节码插桩

  16.                 if ("method".equals(name) && "()V".equals(desc)) {

  17.                     mv = new MethodVisitor(Opcodes.ASM5, mv) {

  18.                         @Override

  19.                         public void visitCode() {

  20.                             super.visitCode();

  21.                             // 在方法执行之前插入字节码

  22.                             mv.visitMethodInsn(INVOKESTATIC, "Monitor", "start", "()V", false);

  23.                         }

  24.                         @Override

  25.                         public void visitInsn(int opcode) {

  26.                             // 在方法返回之前插入字节码

  27.                             if (opcode == RETURN) {

  28.                                 mv.visitMethodInsn(INVOKESTATIC, "Monitor", "end", "()V", false);

  29.                             }

  30.                             super.visitInsn(opcode);

  31.                         }

  32.                     };

  33.                 }

  34.                 return mv;

  35.             }

  36.         };

  37.         reader.accept(visitor, ClassReader.EXPAND_FRAMES);

  38.         return writer.toByteArray();

  39.     }

  40. }

总结:

感谢每一个认真阅读我文章的人!!!

作为一位过来人也是希望大家少走一些弯路,如果你不想再体验一次学习时找不到资料,没人解答问题,坚持几天便放弃的感受的话,在这里我给大家分享一些自动化测试的学习资源,希望能给你前进的路上带来帮助。

软件测试面试文档

我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

 

          视频文档获取方式:
这份文档和视频资料,对于想从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!以上均可以分享,点下方小卡片即可自行领取。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值