基于代码覆盖率的精准测试提效实践
如果无法正常显示,请先停止浏览器的去广告插件。
1. 基于代码覆盖率的精准测试提效实践
有赞零售 秦阳
2. ⽬目录
• 技术背景
• 检测原理理
• 系统架构
• 案例例分析
3. 技术背景
4. ⼿手⼯工测试⽆无法量量化,依赖业务经验
•
•
•
•
•
•
这些测试⽤用例例够不不够?
这些代码执⾏行行了了没有?
这⾥里里的逻辑遗漏漏了了?
1000 ⾏行行代码
开发
20 个测试⽤用例例
这些⽤用例例是否覆盖所有场景?
这个项⽬目的需求都测到了了吗?
上次新增代码都测到了了吗?
20 个真实场
景测试⽤用例例
测试
5. 单测的维护成本⾼高
模块 总代码 已执⾏行行 覆盖率
⾸首⻚页 300 300 100%
购物⻋车 300 240 80%
设置 200 0 0%
…… …… …… ……
5000 ⾏行行代码
10 个单元测试⽤用例例
6. ⼿手⼯工测试+代码覆盖率
同⼀一个版本的App安装到多个设备进⾏行行测试
不不同版本的App在多个设备中进⾏行行测试
代码合并到发布分⽀支后,⽣生成回归包进⾏行行回归
……
……
功能包A
……
?
模块
总代码 已执⾏行行 覆盖率
⾸首⻚页 300 ? ?
购物⻋车 300 ? ?
设置 200 ? ?
…… …… …… ……
功能包N
……
回归包
7. 检测原理理
8. 三种插桩⽅方式
- 源码插桩
- 中间⽂文件插桩
- 可执⾏行行⽂文件插桩
-
iOS(OC) LLVM 针对IR插桩
Android Jacoco offline插桩技
术,通过ASM修改字节
码插桩
9. iOS编译插桩流程
源代码⽂文件
预处理理程序
预处理理⽂文件
编译插桩(iOS)
汇编⽂文件
汇编程序
⽬目标⽂文件
链接程序
可执⾏行行⽂文件
10. 基本块(Basic Block)
只有⼀一个⼊入⼝口
只有⼀一个出⼝口
只要基本块中第⼀一条指令被执⾏行行,基本块内所有指令都会执⾏行行
-
双层扫描:
遍历所有函数
遍历函数内的基本快
BB1
BB2
BB3
11. 函数中基本块的插桩⽅方法
代码⽚片段
开始
执⾏行行 A
if (需要执⾏行行B)
执⾏行行 B
else
执⾏行行 C
结束
12. 函数中基本块的计数⽅方法
代码⽚片段
初始化计数器器 count[]
开始
计数器器⾃自增 count[0]++
执⾏行行 A
if (需要执⾏行行B)
计数器器⾃自增 count[1]++
执⾏行行 B
else
计数器器⾃自增 count[2]++
执⾏行行 C
计数器器⾃自增 count[3]++
结束
guessNumber=33
1
1
0
0
0
1
1
13. iOS编译插桩环境配置
1 Xcode build 编译配置,启动编译插桩,⽣生成gcno插桩记录⽂文件
(记录着插桩位置)
14. iOS编译插桩环境配置
2 通过Build Phases脚本,在编译成功后上传gcno⽂文件
gcno⽂文件格式
⽂文件结构
源码
函数结构
BB 结构
BB ⾏行行结构
插桩代码
15. iOS编译插桩环境配置
3 保存gdca数据到⽂文件并上传(gcda由程序执⾏行行后⽣生成)
代码⽚片段
初始化计数
开始
计数器器⾃自增
执⾏行行 A
if (需要执⾏行行B)
计数器器⾃自增
执⾏行行 B
gcda⽂文件格式
基本块标识 计数器器信息
count[0]的值
…
else
计数器器⾃自增
执⾏行行 C
计数器器⾃自增
结束
count[1]的值
16. gcda和gcno⽂文件⼀一⼀一对应
17. 系统架构
18. 代码覆盖率平台架构
模块
数据可视化
数据解析
基础解析器器
增量量解析器器
合并解析器器
总代码 已执⾏行行 覆盖率
⾸首⻚页 300 300 100%
购物⻋车 300 240 80%
设置 200 0 0%
…… …… …… ……
代码执⾏行行信息上传
数据采集
覆盖率采集 源码采集
APP构建 APP 分发
Hook脚本 代码插桩
CI
App安装
App功能测试
19. 有赞App构建分发平台
触发构建
提
交
代
码
开发
构建成功
编译插桩(iOS)
字节码插桩(Andorid)
插桩⽂文件和源码打包上传
分
发
下
载
20. 覆盖率信息收集
APP构建系统
插桩记录
源码
分
发
下
载
App信息
设备类型
执⾏行行记录
场景:
1 同样的App包,⼀一个设备上可能多次
上传覆盖率信息
2 同样的App包安装在不不同的设备上,
各⾃自上传覆盖率信息
3 AppID = bundle+branch+commit
21. 覆盖率解析
基本覆盖率
整体覆盖率
周期覆盖率
22. 基本覆盖率
应⽤用场景:同样的App包,单个设备会单次上传覆盖率信息
数据采集
函数名 + 执⾏行行次数
基本块标识 + 执⾏行行次数
⽂文件路路径
函数名 + ⾏行行号
代码插桩信息
代码执⾏行行记录
校验信息
基本块结构 + ⾏行行信息
覆盖率信息解析
iOS: LCOV
Android: Jacoco
单次覆盖率数据
⽂文件路路径
函数名 + ⾏行行号
函数名 + 函数执⾏行行次数
代码⾏行行执⾏行行次数 + ⾏行行号
23. 整体覆盖率
数据采集
应⽤用场景:
同⼀一个App包,单个设备会多次上传覆盖
率信息
同样的App包安装在不不同的设备上,分别
上传覆盖率信息
代码插桩信息
代码执⾏行行记录
覆盖率信息解析
设备 A 覆盖率数据 …… 设备 N 覆盖率数据
整体覆盖率 = 单个App包多设备的覆盖率总和
多设备同⼀一个App整体覆盖率数据
24. 周期覆盖率
应⽤用场景:
⼀一个开发分⽀支上会有多次提交,⽐比如bug修复
多个分⽀支上开发的功能,最后合并在⼀一起发版
周期覆盖率 = ⼀一定周期内App所有包的覆盖率总和
25. 周期覆盖率
版本 A 覆盖率数据
数据预处理理
解析覆盖率数据
解析增量量代码
⾏行行号迁移
……
版本 N 覆盖率数据
覆盖率数据预处理理
CovInfo
⾏行行号分析+迁移
程序源码
git diff
diffFile
解析Diff
diffInfo
版本增量量覆盖率数据
合并
多版本全量量覆盖率数
26. 解析两端覆盖率数据
CovInfoMap
key:/coverageDemo/coverage/hello.c
value:List[CovInfo]
⾏行行号:8 ⾏行行号:15 ⾏行行号:20
执⾏行行次数:1 执⾏行行次数:0 执⾏行行次数:1
函数名:main 函数名:fun1 函数名:fun2
……
⾏行行号:11 ⾏行行号:12 ⾏行行号:17
执⾏行行次数:1 执⾏行行次数:1 执⾏行行次数:0
函数名:空 函数名:空 函数名:空
……
27. 解析增量量代码
SourceCode
#include<stdio.h>
void fun1();
void fun2();
int main(int argc,char* argv[])
{
if(argc>1)
fun1();
else
fun2();
printf(“AAA\n");
printf("BBB\n");
return 0;
}
28. 解析增量量代码
DiffFile
改动范围
删除代码
新增代码
diff --git a/coverage/hello.c b/coverage/
hello.c
index bdfdbce..a48a0fb 100644
--- a/coverage/hello.c
+++ b/coverage/hello.c
@@ -8,6 +8,7 @@ int main(int argc,char*
argv[])
If (argc>1)
fun1();
else
-
fun2();
+
printf(“AAA\n");
+
printf("BBB\n");
return 0;
}
29. 解析增量量代码
List<DiffInfo>
⽂文件路路径:/coverage/hello.c
Diff 类型:修改⽂文件(增删改重)
历史路路径:空(重命名有)
List<DiffBlockInfo>:
起始删除⾏行行号:11
删除⾏行行数:1
起始增加⾏行行号:11
增加⾏行行数:2
30. 覆盖率信息合并
覆盖率A
覆盖率N
?
31. ⾏行行号迁移
A-CovInfoMap
⾏行行号:10
执⾏行行次数:1
函数名:空
List<DiffInfo>
⽂文件路路径:/coverage/
hello.c
Diff 类型:修改⽂文件
历史路路径:空
List<DiffBlockInfo>:
⾏行行号:12 起始删除⾏行行号:11
执⾏行行次数:1 删除⾏行行数:1
函数名:空 起始增加⾏行行号:11
ADiff-CovInfoMap
⾏行行号:10
执⾏行行次数:1
函数名:空
⾏行行号:13
执⾏行行次数:1
函数名:空
增加⾏行行数:2
……
……
32. ⾏行行号迁移
List<DiffInfo>
A-CovInfoMap
⾏行行号:10
执⾏行行次数:1
函数名:空
⾏行行号:12
ADiff-CovInfoMap
⽂文件路路径:/coverage/
hello.c ⾏行行号:??
Diff 类型:修改⽂文件 执⾏行行次数:1
历史路路径:空 函数名:空
List<DiffBlockInfo>:
起始删除⾏行行号:11
删除⾏行行数:1
执⾏行行次数:1 起始增加⾏行行号:11
函数名:空 增加⾏行行数:2
⾏行行号:??
执⾏行行次数:1
函数名:空
起始删除⾏行行号:5
删除⾏行行数:2
……
……
起始删除⾏行行号:18
新增⾏行行数:2
33. 覆盖率合并
AD-CovInfoMap
ADiff-CovInfoMap
D-CovInfoMap
⾏行行号:10 ⾏行行号:10
执⾏行行次数:1 执⾏行行次数:1
函数名:空 函数名:空
⾏行行号:13 ⾏行行号:12
执⾏行行次数:1 执⾏行行次数:1
函数名:空 函数名:空
⾏行行号:10
执⾏行行次数:2
函数名:空
⾏行行号:12
执⾏行行次数:1
函数名:空
⾏行行号:13
执⾏行行次数:1
函数名:空
……
……
34. 数据可视化
覆盖率报表
数据可视化
消息推送
35. 案例例分析
36. 覆盖率数据分析
⽬目录覆盖率信息
YZHDBalancePaymentViewController
代码⽬目录
⽂文件覆盖率
YZHDBalancePaymentViewController.m
37. 覆盖率异常
代码覆盖率异常
38. 增量量覆盖率异常
?
39. 覆盖率的意义
覆盖率⾼高 != 测试质量量⾼高
覆盖率异常 ~= 场景漏漏测(边界和分⽀支)+
代码冗余
40. 规划
开发⾃自测的质量量评估和提交限制
Code review的辅助信息
更更多维度的数据分析报告
41. QA