转转QA 基于 codediff 的差异代码覆盖率统计实现

笑哼 for 转转QA · 2018年10月12日 · 最后由 开开心心 回复于 2022年03月24日 · 7604 次阅读
本帖已被设为精华帖!

作者 | 洪硕果

代码覆盖率概述

基本含义:测试后被测目标已执行的代码数 / 被测目标总的代码数
触发行为:功能测试、接口测试
度量维度:函数覆盖、语句覆盖、判断覆盖、条件覆盖、路径覆盖等
代码覆盖率可以作为需求覆盖率的补充,反向衡量测试充分性

  1. 如何衡量测试覆盖率?
    • 基于需求的测试覆盖率:从需求测试点 -- 测试用例的执行情况衡量
    • 基于代码的测试覆盖率:从代码层面 -- 目标对象的代码执行情况衡量
  2. 全量代码覆盖率存在的问题
    • 开发代码冗余量大,重复代码、废弃代码影响覆盖率值
    • 无法精确评估测试覆盖情况
  3. 差异代码覆盖率优点
    • 针对小需求精准评估测试范围覆盖情况
    • 把控 RD 上线自测情况
    • 设置阈值,作为测试通过的指标

代码覆盖率参考意义

  • 分析未覆盖部分的代码,从而反推在前期测试用例设计是否充分,评估需求理解是否清晰,用例设计是否遗漏。
  • 检测出程序中的废代码,可以逆向反推在代码设计中思维混乱点,提醒开发人员理清代码逻辑关系,提升代码质量。
  • 代码覆盖是一种度量方法,而不是衡量正确性或性能的指标。代码覆盖并不等于测试质量、代码质量,把它作为一种发现未被测试覆盖的代码的手段。

差异代码覆盖率统计方法

  1. 转转的代码覆盖率统计特点
    • 依托 Beetle 管理平台
    • 利用测试服务器 agent 服务能力
    • code diff 功能
    • 实现一种平台页面化配置和查看全量及差异代码覆盖率的能力
    • 核心:统计被测目标已执行的代码数,收集被测目标的执行轨迹信息
  2. 采用开源项目 jacoco( Java Code Coverage )
    • Jcoco 实现覆盖率统计的方式:字节码插桩, 有 On-The-Fly 在线和 Offine 离线两种方式
    • On-The-Fly:不需要改源代码、实时收集执行信息、需要启动 java 代理
    • Offline: 提前对文件插桩、停机获取执行信息、不需要代理
  3. 采用在线 On-the-fly 方式
    • JVM 中通过-javaagent 参数指定 jacocoagent.jar 文件启动 Instrumentation 的代理程序
    • 代理程序在 Class Loader 加载一个 class 文件前,将统计探针(boolean 型数组)插入 class 文件中
    • 字节码指令触发时探针进行输入设置为 true
  4. 转转的差异代码覆盖率实现结构
  5. 全量覆盖率
    • 当前部署版本执行测试后的代码覆盖情况,不会累加以前版本的覆盖情况;
    • 直接采用 jacoco 生成的完整覆盖率报告,有多种维度的覆盖率计数;
  6. 差异覆盖率
    • 开发代码改动点的行覆盖情况;
    • 差异覆盖率 = 已覆盖的差异代码行数/总的差异行数;
    • 取改动代码的行号,解析 jacoco 覆盖率报告
    • 会累加以前版本的测试覆盖情况;
  7. Jacoco 覆盖率报告解读
    • 提供多维度的计数器:指令级(Instructions,C0 coverage)、分支(Branches,C1 coverage)、圈复杂度(Cyclomatic Complexity)、行(Lines)、方法(Non-abstract Methods)、类(Classes)
    • 分支覆盖(分支被执行情况): 红色钻石:无覆盖;黄色钻石:部分覆盖;绿色钻石:全覆盖;
    • 行覆盖(字节码指令被执行情况):红色背景:无覆盖;黄色背景:部分覆盖;绿色背影:全覆盖;
  8. 差异覆盖率统计方法
    • 差异对比基础:拉分支时的版本
    • 累加上次版本:版本 n 未覆盖行,查询版本 n-1,若存在并已覆盖,判断 last diff 是否存在,不存在则设置该行已覆盖,其它则未覆盖; 存在两次遍历,计算比较耗时;
  9. 差异覆盖率报告解读
    • 统计范围:service 包中的可执行语句,配置文件、import/注释语句均不计入;
    • 行覆盖统计标准:只要该行被执行过,就认为已覆盖;

覆盖率统计与 SCF 服务代码冲突解决方法

  1. 插桩方法:向类中插入一个静态变量 $jacocoData 和静态方法 $jacocoInit()
  2. 插桩对代码影响:
    • 影响 1:变量 $jacocoData 缺少缺少 get 和 set 方法,数据库实体类进行操作时报错;
    • 影响 2: 使用时遍历成员变量,不识别 $jacocoData 变量,代码解析报错。 影响对象:实体类;解决方法:过滤掉这些类,不进行插桩 具体实现:更改 jacoco 源码,加载类时根据匹配规则判断是否进行插桩,匹配规则:类注解匹配( SCFSerializable 、 Table 、Data);类方法匹配(判断是否仅有 get、set 方法);类名匹配;匹配规则更新方式:配置文件形式存在,有更新时,动态推送到各测试服务器
共收到 27 条回复 时间 点赞
卡斯 将本帖设为了精华贴 10月13日 08:56

学习了,入门基础篇

我也是用类似方法实现的,😀

😀 对于插桩引起的异常,我们是只有针对反射的时候才会抛出异常,方便详细问下你们这个【影响 1】是有用到 get/set 注解还是其他情况?

影响 1 就是 jacoco 插入的变量没有 get 和 set 方法引起的 也是反射引起的

最近也在搞这个,code diff 可以用仓库工具的命令获取,启动时用 includes 指定即可

如果不是 java 的代码,php 的后台呢,用什么工具检查?

这个是针对服务端代码,还是针对客户端 Android 代码?听说 Android 代码不支持在线动态插桩

我去催饭 回复

不知道咧, jacoco 只能用于 java 代码

heygrl 回复

是针对服务端代码的 客户端覆盖率统计可以采用 jacoco 另一种插桩模式

笑哼 回复

累加上次版本:版本 n 未覆盖行,查询版本 n-1,若存在并已覆盖,判断 last diff 是否存在,不存在则设置该行已覆盖,其它则未覆盖; 存在两次遍历,计算比较耗时

这块你们是如何实现呢,是解析两个版本的 html report 自己计算?
还是解析 ec 二进制文件?

笑哼 #12 · 2018年11月22日 Author

解析两个版本的 xml report,再对比两版本的差异计算的

笑哼 回复

那就只能做到方法级别是否覆盖了吧
没法做到源码代码行级别的展现

我们也在做,最后发现也只能这样了

笑哼 #14 · 2018年11月29日 Author

不是 细粒度到行级别的

笑哼 回复

但是 xml report 是没有源码行数据的吧
只是方法 a 的各种覆盖率数值是多少

比如版本 1 的方法 a 覆盖了 3 行
版本 2 的方法 a 覆盖了 2 行
你怎么做合并呢

笑哼 #16 · 2018年12月05日 Author

xml report 有源码行数覆盖信息的

笑哼 回复

还真是,之前就看了头部的几十行。。。
多谢哈

@ 张全蛋 php 有类似的 phpunit 和 xdebug 可以实现, 我最近也在研究 php 这个代码覆盖率, 有兴趣可以一块讨论下

累加上次版本:版本 n 未覆盖行,查询版本 n-1,若存在并已覆盖,判断 last diff 是否存在,不存在则设置该行已覆盖,其它则未覆盖; 存在两次遍历,计算比较耗时;

在行级别合并,还是会有些问题
比如本次覆盖率有这条数据

<line nr="69" mi="3" ci="7" mb="4" cb="2" />

这行是覆盖了的
看你的实现逻辑,这行直接就取本次的数据了是吧?
但是其中有些指令和分支是 missed
按理说还是应该合并上次的该行数据

但是如果想累加上次的行数据
又没法实现
比如上次相同行数据

<line nr="69" mi="7" ci="3" mb="2" cb="4" />

并不能简单的累加,因为你没法知道两次 covered 的数值是不是相同的指令或分支

所以我理解是,只能累加出基于行数据的覆盖率数据(行、方法、类)
基于指令的(指令、分支、圈复杂度)起码从 xml 中是没法准确实现的
不过我们最后看报告,实际看的又是基于代码行的
只不过指令、分支、圈复杂度这几个覆盖率数值是不准确的而已

simple 专栏文章:[精华帖] 社区历年精华帖分类归总 中提及了此贴 12月13日 14:44
ivy520 [该话题已被删除] 中提及了此贴 12月16日 22:55
ivy520 [该话题已被删除] 中提及了此贴 12月16日 23:53
安涛 [该话题已被删除] 中提及了此贴 12月21日 16:29
simple [精彩盘点] TesterHome 社区 2018 年 度精华帖 中提及了此贴 01月07日 12:08

” 根据配置判断是否启用 jacoco,及分配端口号 “, 问一下,假如启用 jacoco,端口号是自动分配的?分配策略是怎样的?如果是自动分配的,codecoverage 配置是如何知道这个端口的,因为如果是 tcpserver 的模式,是需要指定 ip 和端口来获取 exec 文件的。还有,jacoco 的启用时自动执行的吗?不需要手动改 tomat 的启动脚本吗?这部分你们是怎么做的呢

在 Android 端你这个如何实现增量覆盖的,还是没看明白···拿到差异化文件之后,怎么执行 jacoco 命令重新计算的

请问下,code diff 如何拿到拉分支时的版本?

code diff 用的是什么?

hello JaCoCo 增量覆盖率二次开发 中提及了此贴 08月19日 17:48
笑哼 #26 · 2020年07月10日 Author

这是服务端的

请问下,加载类时根据匹配规则判断是否进行插桩,这个源码改了哪些模块?

shootingstar 回复

请问你怎么实现的,可以加微信请教下吗

请教一下,你的 code diff 是怎么做的啊?我用 Jgit 尝试了一下,发现遇到合并分支的 diff 文件就会不准确。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册