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

发表于:2024-4-02 09:38

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:派大星    来源:码上遇见你

  高手回答
  我们在进行单元测试时,经常需要关注一个覆盖率的指标,许多发布流程甚至要求达到特定的百分比。
  那么,单元测试覆盖率是如何统计的呢?其底层实现原理又是怎样的呢?
  单元测试覆盖率的统计原理实际上是通过字节码插桩实现的。也就是说,在编译期间会向代码中注入一些特殊的监控代码,以记录测试执行过程中代码的执行情况,从而推断代码的覆盖情况。这些监控代码能在运行时记录代码的执行情况,也能在编译时生成代码覆盖率报告。
  常见的单元测试覆盖率统计工具包括JaCoCo、Emma、Cobertura等,这些工具能够在编译或运行时对代码进行插桩,并记录代码的执行情况,最终生成覆盖率报告。
  具体见下表:
  什么是字节码插桩
  Java字节码插桩技术是指在编译期或运行期,通过修改Java字节码的方式,在代码中插入额外的代码。这种技术可以在不改变Java源代码的情况下,对Java应用程序的运行时行为进行监控、调试、分析和优化等操作。举例来说,它可以用于实现性能监控、代码覆盖率检测、代码安全扫描等功能。
  字节码插桩技术通常包括以下几个步骤:
  1. 生成目标类的字节码,这一步可以通过Java编译器(如javac)或其他工具(如AspectJ)来完成。
  2. 解析字节码,识别需要进行插桩的代码区域(如方法、循环、异常处理等)。
  3. 插入额外的字节码,通常通过编写Java代码来实现这一步,然后利用字节码生成库(如ASM、Javassist等)生成相应的字节码。
  4. 将修改后的字节码重新写回到磁盘或内存中,以供后续使用。
  假设我们希望对一个Java方法进行性能监控,我们可以在方法的入口和出口处分别插入计时器,以统计方法的执行时间。以下代码展示了如何实现这一功能:
  public class Monitor {
      public static void start() {
          long startTime = System.nanoTime();
          // 将起始时间记录到ThreadLocal中,以便在方法返回时进行计算
          ThreadLocalHolder.set("startTime", startTime);
      }
      public static void end() {
          long endTime = System.nanoTime();
          // 获取起始时间
          long startTime = (long) ThreadLocalHolder.get("startTime");
          // 计算方法执行时间
          long elapsedTime = endTime - startTime;
          System.out.println("Method execution time: " + elapsedTime + "ns");
      }
  }
  public class Example {
      public void method() {
          Monitor.start();
          // 执行方法逻辑
          Monitor.end();
      }
  }
  然而,若需监控多个方法的性能,分别在每个方法中插入Monitor.start()和Monitor.end()将导致代码重复、可读性下降,并存在遗漏的风险。在这种情况下,可以借助字节码插桩技术,在编译期或运行期间自动向每个方法的入口和出口处插入Monitor.start()和Monitor.end(),以确保代码的统一性和可维护性。
  具体实现可借助字节码生成库ASM或Javassist来实现,此处以ASM为例。以下代码展示了如何使用ASM对Example类进行字节码插桩:
  import org.objectweb.asm.ClassReader;
  import org.objectweb.asm.ClassVisitor;
  import org.objectweb.asm.ClassWriter;
  import org.objectweb.asm.MethodVisitor;
  import org.objectweb.asm.Opcodes;
  import java.io.IOException;
  public class MonitorTransformer implements Opcodes {
      public static byte[] transform(byte[] classBytes) throws IOException {
          ClassReader reader = new ClassReader(classBytes);
          ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
          ClassVisitor visitor = new ClassVisitor(Opcodes.ASM5, writer) {
              @Override
              public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
                  MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
                  // 只为指定方法添加字节码插桩
                  if ("method".equals(name) && "()V".equals(desc)) {
                      mv = new MethodVisitor(Opcodes.ASM5, mv) {
                          @Override
                          public void visitCode() {
                              super.visitCode();
                              // 在方法执行之前插入字节码
                              mv.visitMethodInsn(INVOKESTATIC, "Monitor", "start", "()V", false);
                          }
                          @Override
                          public void visitInsn(int opcode) {
                              // 在方法返回之前插入字节码
                              if (opcode == RETURN) {
                                  mv.visitMethodInsn(INVOKESTATIC, "Monitor", "end", "()V", false);
                              }
                              super.visitInsn(opcode);
                          }
                      };
                  }
                  return mv;
              }
          };
          reader.accept(visitor, ClassReader.EXPAND_FRAMES);
          return writer.toByteArray();
      }
  }
  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号