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

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

作者 | 洪硕果

代码覆盖率概述

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

  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 代码

笑哼 #10 · 2018年11月13日 Author
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
笑哼 #31 · 2020年07月10日 Author

这是服务端的

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

shootingstar 回复

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

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

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