zoukankan      html  css  js  c++  java
  • 转转App代码覆盖率方案

    作者|张志阳

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

    目的

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

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

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

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

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

    方案选择

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

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

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

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

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

    方案构思

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

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

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

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

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

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

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

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

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

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

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

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

    4. 数据合并计算、存储

    实现

    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代码覆盖率方案 就先给大家介绍的这里,希望能对大家有所帮助。

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

    
    
    
  • 相关阅读:
    crontab自动备份MySQL数据库并删除5天前备份
    使用ShowDoc在线管理API接口文档
    概率计算(抽奖活动、命中率)
    保护隐私?找回已记住的秘密?你的余额宝、淘宝还安全吗?
    自制公众平台Web Api(微信)
    我为什么期待M#?
    在.net中为什么第一次执行会慢?
    记”Uri.IsWellFormedUriString”中的BUG
    公司ERP系统重构那些事
    Koala Framework是什么?我为什么要写这个框架?
  • 原文地址:https://www.cnblogs.com/finer/p/14127632.html
Copyright © 2011-2022 走看看