转转App代码覆盖率方案

作者|张志阳

代码覆盖率是业内常用的统计代码被执行程度的手段。覆盖率数据结果是对测试工作的一种保证,可以赢得信任、增加上线信心。今天就让我们来聊聊 转转App代码覆盖率方案的实现及应用,希望可以给大家带来一些思路和参考。

目的

在转转客户端团队,搭建代码覆盖率方案的主要目的:

通过代码被执行的比例程度,表现测试工作的覆盖程度

通过未覆盖内容的分析&补充测试,保证测试覆盖程度

通过分析无法覆盖的内容,判断代码设计的合理性

这里需要补充强调一点:覆盖率只是一种度量测试完整性的手段, 是一种测试有效性的度量,代码覆盖率100%也并不代表不会有其他的问题。

方案选择

当我们想实现一套专项测试方案的时候,首先都会想到“在业内有没有公开的、流行的、可用的方案,可供参考或者 直接可以”拿来主义”。

首先,我们可以在搜索引擎、论坛上去进行搜索 “App代码覆盖率”, 结果发现每种语言都有自己的覆盖率数据收集方式。

比如现在服务端代码覆盖率使用的Jacoco,是一套非常成熟的Java 方案。

python 、C++、OC 也都有自己体系的覆盖率收集方案,但并没有Jacoco 那么方便集成。更没有已经将多种语言的覆盖率统计集成在一起的方案。

在调研&对比几种方案后,都会发现了一些缺点、功能缺失 和不方便使用的地方,所以开始考虑,能否从0实现一套完全适用自己团队的代码覆盖率方案。

方案构思

在常见的代码结构中,“行”是代码工程的最小单元,所以想要实现代码覆盖率方案来统计代码的被覆盖程度,首先我们应该都会想到,我们需要统计所有“代码行”的覆盖情况,“行”应该是最小的统计单元。

在大家日常的测试工作中,应该经常会做 埋点测试,简单的说,就是通过埋点代码记录某个页面入口/操作/事件 的触发 或者结果的返回,当页面入口/操作/事件 被触发后,记录&上报,埋点系统统计计数,进行业务层次的数据分析。

这和我们当前要实现的代码覆盖率方案需求有些类似,我们想要在代码行被执行后,进行记录&上报,覆盖率系统进行统计,进行代码层次的数据分析,与埋点对比,代码覆盖程度更广一些,但是实现的方式是比较类似的,就是在需要关注的 代码/事件后增加 用于“记录”的代码块,这种添加代码的方式,就是常说的“插桩”(* 基本的原则:保证被测程序原有逻辑的完整性),插入的代码块一般称之为“探针”。

所以想要实现一套代码覆盖率方案的基础就是:

在保证被测App原有代码逻辑的完整性的前提下,通过代码插桩的方式,在每行源代码后插入“探针”代码,记录对应代码行已被执行

将记录的代码覆盖数据上报给覆盖率服务

覆盖率服务 将数据进行保存 & 合并计算

结合团队内的实际方案需求,我们需要先实现以下部分:

实现Android、iOS工程的代码插桩

探针代码被执行时 记录对应代码的执行状态

为了统一Android 、iOS 覆盖率数据的计算及使用方式,需要统一记录时使用的数据结构

代码执行状态数据上报、接收、存储

数据合并计算、存储

实现

1、插桩:

(1)需要解决的难点

难点1: 准确插桩,保证被测App原有代码逻辑的完整性

在日常看到的代码中我们发现,不同的语言,会对代码格式/语法都有不同的要求/规范,每个人在写代码时的习惯也有所不同

在不对代码内容进行语义分析就进行随意插桩,可能会导致编译失败/ 影响原有代码逻辑

语义分析需要对 代码格式/语法 有足够的掌握程度 & 大量的尝试保证语义分析的足够全面

难点2: 所有代码行都进行插桩,会不会影响客户端性能

答案是必然的,当前客户端的代码量,至少已经是百万级别了

如果在每行代码前后插入探针代码、在App实际运行过程中、频繁的IO运算工作,App的 稳定性、性能都不敢保证

(2)解题方案

准确插桩,保证被测App原有代码逻辑的完整性

由客户端RD同学负责插桩方案的具体实现,即高效又可靠

Android: 使用 ASM 对字节码进行分析 ,再进行准确插桩

iOS: 通过语义分析,判断插桩位置,再进行准确插桩

所有代码行都进行插桩,会不会影响客户端性能

初期方案,我们选择先对逻辑分支进行插桩,大量减少插桩量

优点:控制了插桩数量,减少了对工程性能的影响

弱点:只能采集到进入分支,不能判断分支结束;不能按行统计计算, 不能结合Code Diff 直接判断 新增/修改代码的覆盖情况

(3)插桩前后代码对比

Android

iOS

(4)探针代码解读

标记逻辑分支被执行(逻辑分支全局编号)

全局编号从哪来?

在遍历所有类文件时,对识别出的逻辑分支进行编号

插入位置怎么获得

Android:字节码中有行号的描述,通过ASM解析可以获取

iOS:代码遍历,判断出逻辑分支时,就已经知道当前行行号

(5)插桩流程

获取方法路径和方法签名(用于多个tag之间逻辑块执行数据的比较和合并)

方法路径:类名+方法名可以确定当前工程中一个唯一的方法:

com.wuba.zhuanzhuan.activity.AboutZhuanzhuanActivityonCreate(Landroid/os/Bundle;)

方法签名:对方法内的所有字节码做MD5编码,如果方法逻辑有修改(增减),那么签名就会变化

Tag间对比:如果相同方法路径的签名有变化,则认为该方法内的所有逻辑分支都需要重新覆盖测试

找到逻辑块

获取逻辑块行号

逻辑块编号

将方法路径、方法签名、逻辑块信息存入methodMapping文件

插桩结束后,将methodMapping文件上传到服务器

2、记录代码被执行:

(1)数据存储:为了不影响原始代码的执行效率,探针代码执行记录数据的读写效率必须得到保证,所以我们的选择是:

Android:使用字节数组(Bitset) 存储 探针代码执行记录数据

iOS : 在内存中申请指定长度的数组空间 存储 探针代码执行记录

数组长度?

逻辑块编号完成后,可以知道逻辑块总数,一个字节可以存储8个逻辑块编号,即可计算出需要的数组长度

大约会占多少空间?

EXP: 20W 逻辑块 = 2W5 (20W / 8) 字节 = 24.4KB (2W5 / 1024)

(2)探针代码被执行:

探针代码中,入参就是 逻辑块 在所有逻辑块的中编号

将数组中编号对应的位 置为 1 , 代表已覆盖

3、数据上报、接收、存储:

(1)什么时候上报数据

如果频繁上报,可能会影响App的正常使用,也并没有必要。所以考虑在一些 察觉不到/不太Care的 操作节点进行数据上报。

Android:页面的创建、不可见、销毁 时 上报

iOS:App 启动、退后台、唤醒 时上报

(2)上报哪些内容

project: 与 代码覆盖率服务约定的唯一 覆盖率项目名,区分终端,如zhuanzhuanAndroid / zhuanzhuanIos

version: 版本号

uniqueId: methodMapping 文件上传时时间戳,文件的唯一标识

record: 数组数据

(3)数据存储

服务端接收数据后,根据project、version、uniqueId,查询出记录的 record数据,与上报的record 数据 做按位或计算,即 只要有1出现,该位即为1,已覆盖,再将结果存储起来

所以覆盖率数据都是按 uniqueId 进行区分存储的,即每个安装包的覆盖率数据都单独存储。

4、数据合并计算:

(1)两个安装包的覆盖率数据如何合并

数据库中存储的覆盖率数据是每个安装包的数据以及基础信息,当两个安装包并不是同一个Tag,有代码差异的时候,是不能直接进行按位或计算合并数据的。

这时 methodMapping文件就起到了作用。查找到两个安装包对应的methodMapping文件,对其中的内容进行解析,就可以分别获得两个安装包中的所有类名、方法名、方法签名等信息. 通过循环对比,即可进行数据合并:

为了区分两个不同Tag的安装包,方便后续描述的理解,这里将Tag创建时间较早的安装包称之为Old, 将Tag创建时间较晚的安装包称之为New.

遍历New 的Mapping数据

如果Old 的Mapping数据中 有相同的方法签名,则方法内部的所有逻辑分支进行被执行状态的合并计算

如果Old 的Mapping数据中没有相同的方法签名,则认为该方法为新增/修改过的方法,被执行状态以 New 的覆盖率数据为准

最终会得到以New 的代码为准的覆盖率数据,即相对较新代码的整体覆盖率数据

(2)要合并哪些安装包

我们存储了那么多安装包的覆盖率数据,那么在实际的客户端迭代流程中,我们应该合并哪些安装包的数据,才可以帮助我们进行分析,实现我们的方案目的呢?

我们对 单次打包纬度、单Tag纬度、版本纬度、分支纬度 几种统计纬度进行了利弊分析&对比,最后我们选择使用分支纬度进行覆盖率数据的汇总统计,即根据Tag的前后节点关系,合并同一分支线上的所有Tag(以前一版本的发版Tag为起点,以当前分支线中最新的Tag为终点)的所有安装包的覆盖率数据。

(3)多个安装包的数据集如何快速合并

为了保证合并计算效率,采用多线程来处理。

以前一版本的发版Tag为起点,以当前分支线中最新的Tag为终点,根据记录的Tag前后节点关系,查询出分支线中的Tag列表,再查询出对应的所有安装包覆盖率数据集

多线程分别合并数据集

流程

1、简化流程

图片

2、打包流程

图片

3、测试过程中上报流程

4、自动计算流程

平台功能

1、查看指定版本、分支的增量代码覆盖率数据 & 依赖组件工程的增量代码覆盖率数据

2、查看组件工程中详细的类增量覆盖率数据
3、查看类增量代码中逻辑分支的详细覆盖状态 & 实时计算、渲染

4、选择Tag区间,临时计算增量覆盖率数据
5、选择同版本两个Tag ,对比计算增量覆盖率数据

后续规划

分支统计纬度的优缺点前面已经提到过,接下来我们会增加行覆盖和方法覆盖统计纬度,并且会结合Git diff,计算/渲染 Diff 代码的覆盖率数据。

现在iOS的插桩方案有些简单粗暴,效率也并不高,所以已经开始着手重构。

我们也计划在平台上增加更多的人性化的功能,提升覆盖率数据的分析效率、提升整体的使用体验。

好了,转转App代码覆盖率方案 就先给大家介绍的这里,希望能对大家有所帮助。

如果喜欢我们分享的内容,欢迎点赞、在看、分享~

阅读原文


作者简介: 知识分享时代, 带你从新视角认识「转转QA」。欢迎关注微信公众号:转转QA

声明:文中观点不代表本站立场。本文传送门:https://eyangzhen.com/193697.html

联系我们
联系我们
分享本页
返回顶部