问题背景
近年来,移动应用程序的数量呈现爆炸性增长,随之而来的是用户对应用程序质量的高要求。确保应用程序的质量对于维护用户忠诚度和业务成功至关重要。然而传统的人工测试方法存在一些挑战,包括耗费大量时间和资源、可扩展性和可维护性的限制等,因此移动应用的自动化测试工具应运而生。随着人工智能时代的到来,软件测试领域不断向智能化发展,其中自动测试生成的能力一直是学术和工业界共同关注的研究热点,依靠自动生成测试能较大程度减少测试脚本的编写与维护工作量。
在实际应用中,工业级应用程序经常需要进行更新以适应不断变化的用户需求,例如,工业厂商一般每周都会发布一个新的核心应用版本,然而采用现有的自动化测试工具在这种情况下效果一般,它们仅仅是简单地在移动应用上重新运行一次测试,缺乏对人类经验和知识的运用。Fastbot正是一款结合了强化学习和基于模型决策算法的自动化安卓测试工具,它由字节跳动软件工程实验室(https://se-research.bytedance.com/)团队提出,旨在利用强化学习的技术,通过学习和推理从之前的测试运行中获得知识,从而达到更快更高效的测试效果。此外,Fastbot还提供了个性化的专家系统,用户可以自定义各种配置去满足不同的测试场景和需求。
下面将逐一介绍工具的工作流程、核心概念、算法原理、设计实现以及常用配置,深入了解其在提升移动应用测试效率的能力。
工作流程
Fastbot 是一种利用强化学习的可复用的基于模型的自动化安卓测试工具。它接受一个给定的应用程序版本,以 APK 文件的形式作为输入,并输出覆盖报告和找到的崩溃。Fastbot 的工作流程包括两个主要阶段,如图所示:
(a) 测试前的设置。a1 对 APK 文件进行反编译,收集控件的静态文本信息。a2 在一组设备上安装 APK,同时 a3 加载先前测试运行中的历史测试数据填充模型。
(b) 引导式 UI 探索。b1 从被测应用程序中获取当前的 GUI 页面,b2 识别和抽象出当前页面上的可用 hyper-event(超事件,一组具有相同属性的事件,将在下文具体解释)。b3 选择一个具体的 UI 事件,该事件有可能增加 Activity 覆盖率,b4 执行该事件。执行完毕后,b5 更新历史测试数据、概率模型,同时 b6 更新强化学习智能体。
这些步骤将循环迭代,直到使用完时间预算。
核心概念
技术和算法原理
Fastbot 的关键思想是利用存储在概率模型中的先验知识,去有效地指导 GUI 测试。为了实现这一点,关键步骤是决定在当前 GUI 页面上选择哪个 UI 事件,可以快速地提高 Activity 覆盖率。具体而言,给定一个 GUI 页面,Fastbot 提取当前所有可用的超事件,并采用以下两种策略协同组合去选择执行的事件:(1)基于模型的事件选择,(2)基于学习的事件选择。
1. 基于概率模型的事件选择
为了平衡模型对未知动作的探索和已知高回报动作的利用,避免陷入局部最优的僵局,基于模型的事件选择包括两种模式:模型扩展和模型利用。
模型扩展: 如果当前 GUI 页面中的某些超事件尚未包含在概率模型𝑀中,Fastbot 将启动扩展策略,随机选择一个尚未执行的超事件。在实际中有两种可能的情况:1)先前的测试运行可能没有覆盖所有的超事件,2)当前测试应用程序版本中添加了一些新的功能。该模式可以帮助扩展模型并优先探索潜在的新功能。
Fastbot 的算法原理在于利用先前的知识和概率模型,通过模型扩展和模型利用两种策略模式选择事件,以快速提高 GUI 测试的 Activity 覆盖率。该算法的设计使得 GUI 测试能够更高效地进行,并能够适应不同的应用场景。
2. 基于强化学习的事件选择
前面概率模型只能表达一步的指导信息,而强化学习技术能够将一步扩展为多步的指导信息。Fastbot 采用了 Sarsa N-Step 算法作为奖励函数去计算和更新 Q 值。
3. 案例解释
为了加深理解,下面用头条应用来对算法的几种决策进行解释:
概率模型的探索与利用模式
通过对历史数据记载对概率模型 M 初始化,见图 b,启动头条应用后,进入 home Activity1,当前页面可以抽象为 3 个 hyper-event,这三个事件都被包含在模型中,因此 Fastbot 回启动模型利用策略选择事件,看图 b 左边部分,可以知道 e3 有 90%的概率留在 Activity1,因此更倾向于选择 e1 和 e2,假设选择 e1 后,到达 Activity2,在 Activity2 中 e4 已经在模型中,并且 100%回到 Activity1,同时 e5,e6 不再模型中,此时 Fastbot 将启动探索模式,随机选择 e5 或者 e6,如果选择 e5,到达 Activity3,此时模型添加一条 e5-Activity3 的概率值 100%,同时由于 e1 的执行次数加 1,因此 e1 的概率也要改变,e1-Activity2 的概率变为 0.7/1.1=63.6%,e1-Activity5 的概率变为 36.4%,更新为图 b 右半部分。
Q 表的更新利用
在图 a 中,Activity2,e4,e6 没有被执行过,同时 e6 不在模型内,因此 e1 的奖励更高,相似的,e7,e8 也是新的事件,因此 e5 的奖励会更高,假设 e1,e2,e3 都会执行过很多次,并且 Activity2,3,4 都被覆盖了,此时将利用 Q 表中的值去计算事件选择的概率,在当前 Activity1 中,由于 e1 的奖励最高说明他能够到达更深的 Activity,因此选择 e1 作为下一个事件执行。
设计实现
Fastbot 开源版包括客户端和本地服务端两部分,客户端负责监听 UI 事件,接收和注入相应的动作,服务端负责计算和决策。具体而言,在每个设备上运行 Fastbot 客户端,通过监控 GUI 页面信息发送给服务端,服务端接收信息根据算法决策返回选择的事件,客户端接收事件并执行操作。
客户端使用 Java 语言编写,用于获取 GUI 信息,读取服务端决策,并将决策转化为设备可以执行的代码,从而操作设备;本地服务端,即与 Java 层相对应的底层 C/C++代码层,以动态链接库(.so 文件)的形式与 Java 层通过 JNI 接口进行交互,它用于实现用户偏好的读取,模型的学习和任务决策,并将决策结果转化为 JSON 格式的信息传递给 Java 层。
Fastbot 的实现继承 Monkey 原生框架,下图展示了 Fastbot 扩展的代码简要结构图,在 java 层新增了来自 Fastbot 算法返回的事件源 MonkeySourceApeNative 类,对 GUI 树的获取,以及对 Android 不同的系统的兼容的 Adapter 适配接口,此外,为了支持自定义操作的解析和执行对 Monkey 的原始事件进行了封装。
常用配置
1. 配置自定义事件
Fastbot 支持自定义事件序列,适用于场景覆盖不全,通过人工配置到达 Fastbot 遍历不到的场景。
配置步骤:
- 新建 max.xpath.actions 文件(文件名称固定不能更改)
- 参照案例格式指定控件和相应的动作。其中:
- prob:发生概率,"prob":1,代表发生概率为 100%
- activity:所属场景
- times:重复次数,默认为 1 即可
- actions:具体需要执行的事件序列,其中要指名每个操作对象的 xpath,对应的动作 action,和与下一个事件间隔的时间 throttle(ms),注意 xpath 写法
- 动作类型(必须大写):
CLICK:点击,想要输入内容在 action 下补充 text,如果有 text 则执行文本输入
LONG_CLICK:长按
BACK:返回
SCROLL_TOP_DOWN:从上向下滚动
SCROLL_BOTTOM_UP:从下向上滑动
SCROLL_LEFT_RIGHT:从左向右滑动
SCROLL_RIGHT_LEFT:从右向左滑动
配置完成后,将配置文件推送到手机端:
adb push 路径+max.xpath.actions /sdcard
下面以 AmazeFileManager 为例:
- 第一种情况:当事件执行不涉及 Activity 的跳转时,只需将所有的事件序列写在一个对象中。
如图所示,actions 字段里的 4 个动作分别对应下图中红框标出的动作,(1)点击菜单按钮打开菜单栏,(2)点击 recent file 按钮到达相应文件目录,(3)点击加号按钮,打开浮选选项,(4)点击 File 按钮打开新建文件对话框。
[
{
"prob":1,
"activity":"com.amaze.filemanager.ui.activities.MainActivity",
"times":1,
"actions":[
{
"xpath":"//*[@content-desc='Navigate up']",
"action":"CLICK",
"throttle": 2000
},
{
"xpath":"//*[@resource-id='com.amaze.filemanager:id/design_menu_item_text' and @text='Recent files']",
"action":"CLICK",
"throttle": 2000
},
{
"xpath":"//*[@resource-id='com.amaze.filemanager:id/sd_main_fab']",
"action":"CLICK",
"throttle": 2000
},
{
"xpath":"//*[@resource-id='com.amaze.filemanager:id/menu_new_file']",
"action":"CLICK",
"throttle": 2000
}
]
}
]
- 第二种情况:当事件执行涉及到 Activity 的跳转时,要将对应不同 Activity 的事件序列单独存放。
下图展示了从 MainActivity 跳转到 PreferencesActivity 的例子。
可以看到在配置文件中有两个对象,分别对应 MainActivity 和 PreferencesActivity 两个页面下需要执行的事件序列。
[
{
"prob":1,
"activity":"com.amaze.filemanager.ui.activities.MainActivity",
"times":1,
"actions":[
{
"xpath":"//*[@content-desc='Navigate up']",
"action":"CLICK",
"throttle": 2000
},
{
"xpath":"//*[@resource-id='com.amaze.filemanager:id/design_navigation_view']",
"action":"SCROLL_BOTTOM_UP",
"throttle": 2000
},
{
"xpath":"//*[@text='Settings' and @resource-id=['com.amaze.filemanager:id/design_menu_item_text']",
"action":"CLICK",
"throttle": 2000
}
]
},
{
"prob":1,
"activity":"com.amaze.filemanager.ui.activities.PreferencesActivity",
"times":1,
"actions":[
{
"xpath":"//*[@resource-id='android:id/title' and @text='Appearance']",
"action":"CLICK",
"throttle": 2000
}
]
}
]
- 带文本输入的情况
配置格式: 在 action 字段选择“CLICK”,同时添加“text”字段存储需要输入的文本。
以抖音为例,使用以下配置信息可以指定完成输入账户信息的操作。两个动作,分别对应点击 use phone 按钮和输入文本。
[
{
"prob":1,
"activity":"com.ss.android.ugc.aweme.account.login.auth.I18nSignUpActivityWithNoAnimation",
"times":1,
"actions":[
{
"xpath":"//*[@resource-id='com.zhiliaoapp.musically:id/ayo' and @text='Use phone / email / username']",
"action":"CLICK",
"throttle": 2000
}
]
},
{
"prob":1,
"activity":"com.ss.android.ugc.aweme.account.login.v2.ui.SignUpOrLoginActivity",
"times":1,
"actions":[
{
"xpath":"//*[@text='Phone number' and @resource-id=['com.zhiliaoapp.musically:id/e61']",
"action":"CLICK",
"text":"12341828506",
"throttle": 2000
}
]
}
]
2. 屏蔽控件
Fastbot 支持手动配置需要屏蔽的控件或区域,比如测试过程中“半路”中途退出登录,屏蔽退出登录按钮。
配置步骤:
- 新建 max.widget.black 文件(文件名称固定不可更改)
- 参照案例格式指定需要屏蔽的控件,格式如下:
- bounds:屏蔽某个区域,在该区域内的控件或坐标不会被点击。
- xpath:查找匹配的控件,屏蔽点击该控件。
- xpath+bounds:查找匹配的控件,当控件存在时屏蔽指定的区域。
- activity:当 activity 与 currentactivity 一致时执行如下匹配
- 屏蔽控件或区域共有三种方式:
- 配置完成后,将配置文件推送到手机端:
adb push 路径+max.widget.black /sdcard
下面以 AmazeFileManager 为例,展示如何进行控件,区域以及树剪枝屏蔽:
- 屏蔽控件
如图(a)所示,红框标出来的黑色区域控件是手动配置的屏蔽控件。在这里,使用 xpath 来指定需要被屏蔽的控件。
[
{
"activity":"com.ss.android.ugc.aweme.main.MainActivity",
"xpath":"//*[@content-desc='Navigate up']"
},
{
"activity":"com.ss.android.ugc.aweme.main.MainActivity",
"xpath":"//*[@content-desc='More options']"
}
]
- 屏蔽区域
如图(b)所示,红框标出来的黑色区域控件是手动配置的屏蔽区域。在这里使用 bounds 来配置屏蔽区域。
[
{
"activity":"com.ss.android.ugc.aweme.main.MainActivity",
"bounds":"[0,18],[240,60]"
}
]
- 树剪枝屏蔽
树剪枝屏蔽是指在 GUI 树中查找到对应控件,通过将控件属性的 enable 设置为 False,从而使控件屏蔽。
配置步骤:
- 配置 max.tree.pruning 文件(文件名固定不可更改)
- 参照案例格式指定需要屏蔽的控件,格式如下:
- activity:当 activity 与 currentactivity 一致时执行如下匹配
- 剪枝方式:配置 xpath,查找匹配的控件,改变控件属性,从而使控件屏蔽。
- 配置完成后,将配置文件推送到手机端:
adb push 路径+max.tree.pruning /sdcard
例子:仍以 AmazeFileManager 为例, 如上图(c)所示,被红框标出的黑色区域即为手动配置的树剪枝屏蔽控件,可以通过将控件的 enabled 属性变为 flase,也可以将其他属性变为空来实现屏蔽。
[
{
"activity":"com.ss.android.ugc.aweme.main.MainActivity",
"xpath":"//*[@content-desc='Navigate up']",
"enabled":"false"
},
{
"activity":"com.ss.android.ugc.aweme.main.MainActivity",
"xpath":"//*[@content-desc='More options']",
"enabled":"false"
},
{
"activity":"com.ss.android.ugc.aweme.main.MainActivity",
"xpath":"//*[@resource-id='com.amaze.filemanager:id/search'",
"resourceid":"",
"contentdesc":"",
"text":"",
"classname":""
}
]
更多信息
Fastbot 开源地址:https://github.com/bytedance/Fastbot_Android
更多 Fastbot 技术细节请参考该论文:“Fastbot2: Reusable Automated Model-based GUI Testing for Android Enhanced by Reinforcement Learning”。 Zhengwei Lv(吕正伟), Chao Peng(彭超), Zhao Zhang(张钊), Ting Su(苏亭), Kai Liu(刘凯), Ping Yang(杨萍)。 37th IEEE/ACM International Conference on Automated Software Engineering (ASE 2022).