Android 调用链——自动化精准测试

2021年11月26日 阅读数:4
这篇文章主要向大家介绍Android 调用链——自动化精准测试,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

做者:字节跳动终端技术——吴思成前端

1、背景自动化精准测试是指对每次 MR 中改动部分的代码,可以进行自动的准确的测试,从而提升代码的质量保障以及减小测试的人耗。java

1.1 现有流程

常规的开发流程以下:android

为了确保这些变更不会引入 crash,影响线上用户体验,所以须要对这些变更进行测试。算法

测试通常分为开发同窗白盒自测以及测试同窗黑盒测试app

目前测试的流程以下:框架

除去开发自测以外,还须要测试同窗来进行测试,常规的测试手段就是针对应用的每一个  Activity 维度去录制测试用例,在每次提交mr的时候,测试的同窗跑一下测试用例便可。运维

对于每次提交 mr,咱们对代码所发生的变更抽象为以下三种状况:异步

  1. 添加了新的方法
  2. 改动原有方法
  3. 删除方法

对于第一种状况,多是添加了新的逻辑,也多是新增了功能,所以现有的测试用例可能没法覆盖到新功能,须要测试同窗补充录制测试用例。ide

对于后面两种状况,不管是方法的逻辑修改仍是方法删减,现有的测试用例可以覆盖代码改动逻辑,所以测试的同窗只需执行现有的测试用例便可函数

1.2 自动化测试流程

手动执行已有的测试用例实际上是一个重复机械的工做,所以咱们把这个流程改形成了自动化流程:

如上图所示,咱们把自动化测试添加到了 CI 流程当中,依赖于「构建包」任务获取 apk 包。而且还使用到了公司内部的云真机平台,即咱们能够直接经过 http 请求接口让测试机执行咱们的自动化测试脚本,从而执行测试用例。

云真机平台界面

实际流程跑通以后,很快咱们遇到了一些新的问题:

  • 像抖音、头条的团队,天天都有成百上千个 mr,若是每一个 mr 都全量跑测试用例,那么每一个测试会特别耗时且极其耗费云真机资源
  • 此外,其实大部分 mr 的代码改动量并不大,每次可能只涉及几个函数的变更,所以使用全量测试用例显然不合理

所以咱们须要针对每次 mr 去寻找合适的测试用例,精准的推荐到自动化测试流程当中。

2、精准测试方案

问题描述

咱们但愿在每次 mr 的时候,可以推荐和本次 m r变动代码相关的测试用例,从而进行自动化测试。

那么须要面临以下几个问题:

  1. 如何将测试用例和代码关联
  2. 如何获取每次 mr 的变动内容
  3. 如何精准推荐测试用例

2.1 测试用例如何关联代码

测试用例实质是黑盒测试时的点击输入等一系列用户行为的录制,那么咱们如何可以将这些用户行为和实际的代码对应上呢?

连结点其实就在 Activity 上,上文提到录制测试用例时,测试同窗是以 Activity 做为维度进行录制的,那么若是咱们可以知道当前的代码关联哪一个 Activity,就能够只用这个 Activity 的测试用例来测试这段代码,可以有效的减小测试案例的数量,提升测试的效率以及精确度。

那么问题来了,咱们如何知道某段代码关联哪一个 Activity 呢?

这里能够经过生成方法调用链来实现。

2.1.1 什么是方法调用链

就是将一段代码中的全部函数的调用关系经过调用边链接造成图,这个图就是方法调用链图

2.1.2 Android 调用链

若是可以找到 Activity 的直接关联的函数,而且结合方法调用链,咱们就可以找到 Activity 所间接关联的函数。

如图,function1 是 ActivityA 直接关联的函数,那么 function1 这条调用链上的其它函数都间接地与 ActivityA 关联。

咱们称这种具有 Activity 到函数的边的图为 Android 调用链图,下文中咱们会着重地介绍如何生成一个 Android 调用链图。

Android 调用链应具有能力:

  • 从 Activity 查询全部该 Activity 涉及的函数(无层级关系)
  • 从 函数 查询全部涉及该函数的 Activity
  • 查询函数调用关系,一跳,二跳等
  • 查询某个 Activity 的起始函数
  • 查询某个 Acitivity 的下一个 Activity

2.2 获取 mr 变动的内容

可能有人会想:获取变动内容,难道不是求一下 mr 先后 commit 的 diff 就完事了吗?

但其实并无这么简单,由于咱们求出来的 diff 只是增删改的代码段,而单凭代码段是没有办法经过 Android 调用链关联到 Activity 的。Android 调用链的节点是方法,所以咱们实际须要的 mr 变动内容应该是本次mr中发生变动的方法,这里指的方法变动包括:

  1. 方法新增
  2. 方法改动
  3. 方法删减

那么咱们如何知道一次 mr 中有哪些方法发生变更呢?

这里咱们使用到了静态分析的技术,首先获取本次 mr 中全部发生变动的源码文件,以及其对应的变动前的源码文件。而后经过 intellij 的 sdk 将源码文件转化为 psi,最后经过对比 psi 可以获取变动的方法有哪些。

PSI:程序结构接口,是IntelliJ Platform 中的一个语义抽象层,负责解析文件并建立支持平台许多功能的语法和语义代码模型。咱们能够简单的把它理解为是一个抽象语法树,可是它基于java以及kotlin的语言特性作了更细粒度的解析,可以识别出代码中的类、方法、参数、判断符等语义。

所以基于psi,咱们比较两个文件中方法是否发生了变动就会简单不少,比较规则以下:

  1. 新增方法,比较新文件和旧文件中的方法名,若是某方法只在新文件中存在,而旧文件中不存在,则表示该方法为新增方法
  2. 删除方法,比较新文件和旧文件中的方法名,若是某方法只在旧文件中存在,而新文件中不存在,则表示该方法为删除方法
  3. 改动方法,若是新旧文件中都存在该方法,那么分别计算出新旧文件中该方法的 body 的 size,若是 size 不一致,则表示方法发生了变更

2.3 精准推荐测试用例

对于测试用例的推荐,并不只仅只是过滤出相关的 Activity 用例,还会结合 Activity 与此次变动的相关性、Activity 是不是线上热点 Activity、Activity 的发现关联 Crash 的后验几率等信息,去设置 Activity 的测试步数。此外,还会基于测试覆盖率、crash 率、线上用户机型分布等多维度数据对目标 Activiy 的测试机型进行分配。具体的推荐算法流程目前暂不便于对外,敬请期待后续的分享。

3、Android 调用链构建流程

3.1 阶段一:生成全局函数调用链图

简单介绍一下知识背景,调用链是基于静态分析技术实现的,静态分析技术可简单分为源码分析和产物分析,例如 Android 所提供的 Lint 检测就是基于源码分析,而这里生成调用链是基于 apk 分析,也就是产物分析。

目前针对 Java 开源的静态分析框架,主要有 wala 和 Soot,相比 wala,Soot 的文档更多,社区更为活跃,所以咱们最终基于 Soot 进行定制开发

咱们所开发的 Android 精准调用链生成工具——ByteRope,是基于 Soot 定制化开发,Soot 为咱们提供了 CallGraph 的生成能力,可是简单的 CallGraph 并不能知足咱们对于精准关联 Activity 的需求,还需进一步的优化改造。

简单介绍一下调用链生成的算法流程:

调用链生成流程:

  1. 解析 apk,得到apk 中全部的 class
  2. 解析每一个 class,得到 class 中的全部 method
  3. 解析全部 method,得到 method 的 body,body 是由一条条命令语句组成,例如复制、方法调用等
  4. 解析 method 的 body,一旦出现函数调用,就在这个 method 和被调用的 method 之间构建一条边
  5. 当咱们遍历完全部 class 中的全部method's body,那么调用链图也就构建完成了

在构建调用链图过程当中咱们可以拿到的信息:

  1. apk 中所有的类
  2. 每一个类中的方法
  3. 每一个方法的 body
  4. 每一个方法所调用的其余方法(调用边)

3.2 阶段二:构建 Activity-method 调用链路

3.1.1 获取 apk 中的全部 Activity

前面提到,调用链的目的是找到方法所关联的 Activity,从而推荐自动化测试 case,所以咱们须要找到全部的 Activity,将其做为调用链的入口类。

获取 Activity 方法

前面提到,在生成调用链的过程当中,咱们已经拿到了 apk 中全部类的信息,只须要遍历全部类,判断该类是否继承于 android.app.Activity、androidx.appcompat.app.AppCompatActivity,若是继承,则表示该类为 Activity。

3.2.2 生成以 Activity 为入口的调用链

在阶段一中已经生成了全局调用链,这里以 Activity 为入口生成调用链的目的是确认调用链中的函数都是由 Activity 出发连接的,从而确保调用链中的每一个方法都关联了 Activity。

以Activity做为入口生成的调用链

4、调用链的优化:关联 Android 原生组件

前面提到,Soot 为咱们提供了 CallGraph 的生成能力,可是简单的 CallGraph 并不能知足精准关联 Activity 的需求,还需进一步的优化改造。

4.1 背景

Android 中不少组件、控件是经过布局文件或是异步机制调用的,所以即便生成了全局调用链,也难以将这些组件、控件和所属的 Activity 关联起来。

4.1.1 调研

可能会出现这种状况的组件有 Fragment,自定义控件。

Fragment

其中 Fragment 通常分为静态加载动态加载.

  • 静态加载是在 activity 的布局文件中进行载入
  • 动态加载通常是经过 FragmentTransaction.add(fragment).commit() 载入。

自定义控件

通常继承自 View 或 ViewGroup,加载方式也分为静态加载和动态加载

  • 静态加载是在 layout 文件中直接使用控件的全限定名做为 Tag
  • 动态加载

通常是经过 ViewGroup.addView() 将自定义控件装载至目 标ViewGroup 中。

4.2 方案

4.2.1 关于显式调用 Fragment、自定义View

面临的问题:

调用链不会关联系统函数,所以 Fragment、自定义 View 下的 Android 系统 override 方法是不会被关联到的。

建模:

已有的调用链和 Fragment 的系统 override 方法关联起来。

可是因为系统函数原本就没办法直接和其余方法进行关联,所以咱们手动添加一条边,将 caller 方法和 override 方法关联起来。

caller 方法和 override 方法关联

4.2.2 关于静态调用布局文件中的 Android 组件、Fragment

面临的问题:

因为经过布局文件加载的 Android 组件彻底是走Android 系统内部的逻辑,而且是异步调用的方式,所以当前生成的调用链不存在由 Activity 到这些Android 组件的通路,换句话说,这些组件没法找到它们所关联的 Activity,从而致使精准测试没法推荐测试用例。

建模

目的:构建从 Activity 到 被静态调用的组件 的通路。思路:寻找静态调用的衔接点,须要找到布局文件调用Android组件整个流程的全部衔接点,才可以串联成调用链通路:

  1. Activity 经过 setContentView() 设置布局文件:

    可以拿到 Activity 对应的布局文件 id

  1. 找到布局文件 id 与布局文件的映射关系:

    首先,布局文件是存放在 apk 中的,咱们解压 apk,就可以发现布局文件存放在 res 目录下。

    其次,在 apk 中有一个二进制文件名字叫 resources.arcs,这是apk中全部资源信息的集合,咱们所须要的布局文件 id 到布局文件的映射关系就存放在 resources.arcs 文件中。

  2. 解析布局文件:

    首先,须要了解 Activiy是如何在布局文件中使用 Fragment 以及自定义组件的:

    a.布局文件中调用 Fragment

    使用 标签,并在 android:name 属性中引入 Fragment 类名:

    b.布局文件中调用自定义 View
    直接调用自定义控件类做为 layout.xml 的 tag

    c. 布局文件中调用其余布局文件

    i. 经过标签引入其余布局

    ii. 经过 android:layout 属性引入其余布局

其次,解析布局文件,根据上述的 Activity 经过布局文件静态配置组件的方式,设置文件解析规则,从而获取 Activity 到 Fragment、自定义 View 的链路:

至此,Android 调用链关联 Android 原生组件的优化工做已完成。

5、收益

在 5-6 月中,自动化精准测试接入至抖音的 MR 流程,目前已取得了初步的成效:

  1. 抖音 Android: 工具线+基础业务+社交 的测试人效节省35%
  2. 相比普通自动化任务(Activity 覆盖率约 0.5%),自动化精准的 Activity 覆盖率平均提高约15倍,任务平都可以发现约3个 Crash

6、总结与展望

本文首先介绍了自动化精准测试的演变过程,以及咱们在实现自动化精准测试过程当中遇到了哪些问题,及其解决的方案;其次,本文着重介绍了自动化精准测试流程中,Android 调用链做用、性质以及它的构建方式,并介绍了 Android 调用链的优化项,即基于 Android 特性定制化关联 Activity,使得mr变动方法关联 Activity 的准确度提高,从而提升测试用例的推荐准确率,减小没必要要的测试,提升测试人效。

可是 Android 调用链的使用场景远不止于此,它还可以应用于敏感方法的链路追踪、API  调用梳理等场景,但随之而来的是对调用链精确程度的要求提高,所以咱们对 Android 调用链的优化作出以下的几点展望:

  • 完善调用链算法,目前调用链的构建仅局限于同步调用,但在 Android 中存在不少异步调用的逻辑,那么针对这些异步调用的场景,咱们可以经过建模将其覆盖,从而提高调用链的精确度。
  • 生成更细粒度的调用链,目前调用链是以方法粒度进行构建的,可是在方法中存在判断条件致使逻辑分叉,那么若是可以将方法基于代码语义拆分红 BasicBlock 粒度,并进行调用链的构建,就可以使得每条调用链所表示的逻辑信息更加精准,从而也可以在智能测试领域提高推荐测试 case 的准确度。

关于字节跳动终端技术团队

字节跳动终端技术团队(Client Infrastructure)是大前端基础技术的全球化研发团队(分别在北京、上海、杭州、深圳、广州、新加坡和美国山景城设有研发团队),负责整个字节跳动的大前端基础设施建设,提高公司全产品线的性能、稳定性和工程效率;支持的产品包括但不限于抖音、今日头条、西瓜视频、飞书、懂车帝等,在移动端、Web、Desktop等各终端都有深刻研究。

就是如今!客户端/前端/服务端/端智能算法/测试开发 面向全球范围招聘!一块儿来用技术改变世界,感兴趣请联系chenxuwei.cxw@bytedance.com,邮件主题 简历-姓名-求职意向-指望城市-电话

本文提到的自动化精准测试后续将在火山引擎应用开发套件MARS上线,MARS是字节跳动终端技术团队过去九年在抖音、今日头条、西瓜视频、飞书、懂车帝等 App 的研发实践成果,面向移动研发、前端开发、QA、 运维、产品经理、项目经理以及运营角色,提供一站式总体研发解决方案,助力企业研发模式升级,下降企业研发综合成本。可点击连接进入官网了解更多产品信息。