阅读 381

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

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

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

1.1 现有流程

常规的开发流程如下:

为了确保这些变动不会引入 crash,影响线上用户体验,因此需要对这些变动进行测试。

测试一般分为开发同学白盒自测以及测试同学黑盒测试

目前测试的流程如下:

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

对于每次提交 mr,我们对代码所发生的变动抽象为如下三种情况:

  1. 添加了新的方法

  2. 改动原有方法

  3. 删除方法

对于第一种情况,可能是添加了新的逻辑,也可能是新增了功能,因此现有的测试用例可能无法覆盖到新功能,需要测试同学补充录制测试用例。

对于后面两种情况,无论是方法的逻辑修改还是方法删减,现有的测试用例能够覆盖代码改动逻辑,因此测试的同学只需执行现有的测试用例即可

1.2 自动化测试流程

手动执行已有的测试用例其实是一个重复机械的工作,因此我们把这个流程改造成了自动化流程:

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

云真机平台界面

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

  • 像抖音、头条的团队,每天都有成百上千个 mr,如果每个 mr 都全量跑测试用例,那么每个测试会特别耗时且极其耗费云真机资源

  • 此外,其实大部分 mr 的代码改动量并不大,每次可能只涉及几个函数的变动,因此使用全量测试用例显然不合理

因此我们需要针对每次 mr 去寻找合适的测试用例,精准的推荐到自动化测试流程当中。

二、精准测试方案

问题描述

我们希望在每次 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 的测试机型进行分配。具体的推荐算法流程目前暂不便于对外,敬请期待后续的分享。

三、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作为入口生成的调用链

四、调用链的优化:关联 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-6 月中,自动化精准测试接入至抖音的 MR 流程,目前已取得了初步的成效:

  1. 抖音 Android: 工具线+基础业务+社交 的测试人效节省35%

  2. 相比普通自动化任务(Activity 覆盖率约 0.5%),自动化精准的 Activity 覆盖率平均提升约15倍,任务平均可以发现约3个 Crash

六、总结与展望

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

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

  • 完善调用链算法,目前调用链的构建仅局限于同步调用,但在 Android 中存在很多异步调用的逻辑,那么针对这些异步调用的场景,我们能够通过建模将其覆盖,从而提升调用链的精确度。

  • 生成更细粒度的调用链,目前调用链是以方法粒度进行构建的,但是在方法中存在判断条件导致逻辑分叉,那么如果能够将方法基于代码语义拆分成 BasicBlock 粒度,并进行调用链的构建,就能够使得每条调用链所表示的逻辑信息更加精准,从而也能够在智能测试领域提升推荐测试 case 的准确度。

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

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

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

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


作者:字节跳动终端技术
链接:https://juejin.cn/post/7022848499897073694


文章分类
后端
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐