本文主要是介绍干货 | Trip.com 智能自动化探索测试,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
作者简介
祥星,携程Android开发工程师,对Android自动化测试有深入的研究。
一、简介
快速的业务迭代要求快速的App发版节奏,随之而来的是质量保障压力的增大。而增大自动化程度,提升QA效率就是一种非常重要的手段,以适应快速发版的要求。
自动化探索是一种模拟用户行为,不停地在页面上点击、滑动、输入,以期望进入更多页面的一种软件测试方法。这种方法的核心在于能自动化地覆盖到常规case之外的路径,发现预期之外的问题,是众多QA手段中的一环。大家比较熟悉的Monkey是最典型的自动化探索工具,它像猴子一样在屏幕上快速地点击。Monkey的测试思路非常简单:每次从当前页面随机选择一个点(x,y)触发,这一过程一直持续直到结束。
一种典型的应用场景就是通过自动化探索跑到一些corner cases,提前发现App Crash,以降低Crash率。这种场景下对于自动化探索的核心要求就在于所能触达的页面,更进一步地是所能覆盖的软件执行路径。但 Monkey 的问题在于,屏幕上大部分区域都不可点击,其触发的大部分事件都无效,又因为是纯随机触发,事件存在大量冗余。这就导致Monkey的探索效率不高。
因此,我们提出IAET(Intelligence Android Exploration Tool)的智能自动化探索工具,一个能有效检测当前页面元素,智能化展开探索的自动化工具,以尽可能触达更多页面和软件执行路径。
本文主要内容包括:第二章UI驱动,介绍有效元素检测;第三、四章介绍探索算法及优化后的探索算法;第五章介绍工具实现;第六章介绍探索成果;第七章介绍对比实验;第八章总结。
二、UI驱动
2.1 UiAutomator简介
Uiautomator是谷歌推出的UI自动化测试框架,用于模拟点击每个控件元素,并确认输出的结果是否符合预期。UIAUtomator除了根据resource-id、content-desc、text来查找元素之外,还提供了获取页面上所有可点击元素的能力。
2.2 利用UIAutomator查找所有可点击元素
UiAutomator提供的 UiAutomation#getRootInActiveWindow() API能够获取页面上所有元素,效果等同于uiautomatorviewer查看到的页面布局树上的属性。
下面举例如何通过AccessibilityNodeInfo获取当前页面所有点击元素:
// 递归获取当前节点所有可点击的子节点
public static void getCurrentAllClickViews(AccessibilityNodeInfo nodeInfo, List<AccessibilityNodeInfo> list) {if (nodeInfo == null)return;if (isVisible(nodeInfo)) {if (nodeInfo.isClickable() && notEditText(nodeInfo)) {list.add(nodeInfo);}if (nodeInfo.getChildCount() != 0) {for (int i = 0; i < nodeInfo.getChildCount(); i++) {getCurrentAllClickViews(nodeInfo.getChild(i), list);}}}
}
通过上面的方法就能获取一个页面所有可点击的元素。
2.3 运行
我们使用 app_process 运行UIAutomator[1][2]。首先编写一个可以连接UIAutomator的JAVA程序,然后再将JAVA程序push到手机设备中,最后使用app_process启动JAVA进程,与UIAutomator建立桥梁。
使用方式如下:
第一步,创建UIAutomaion桥接类。
public class UiTestAutomationBridge {public void connect() {...mUiAutomation = new UiAutomation(mHandlerThread.getLooper(), new UiAutomationConnection());mUiAutomation.connect();...mUiAutomation.setServiceInfo(info);}public AccessibilityNodeInfo getRootInActiveWindow() {return mUiAutomation.getRootInActiveWindow();}
}
第二步 创建一个Main函数调用connect方法,获取RootNode。
public static void main(String[] args) {UiTestAutomationBridge.getInstance().connect();AccessibilityNodeInfo rootNode = UiTestAutomationBridge.getInstance().getRootInActiveWindow();
}
第三步将JAVA文件打成jar包,使用app_process运行jar包。
adb shell CLASSPATH='/data/local/tmp/iAET.jar' '/system/bin/app_process' '/data/local/tmp/iAET.jar' com.testing.Main
具体如何使用app_process运行JAVA程序,见参考文献[2]
三、探索算法1.0
3.1 App模型图
在理想的模型中,一个无页面状态的App模型,可以刻画成一幅模型图,其中节点代表页面,边代表事件。
以Trip.com首页为例,首页点击机票、酒店和火车按钮分别跳转到机票首页、酒店首页和火车首页。机票、酒店和火车又有自的跳转页面,这一过程一直持续,直到页面无事件为止。
研究App的遍历问题本质上就转化成研究图的遍历问题,因此我们借鉴图的深度遍历算法制定探索策略。策略如下:
页面事件随机触发,触发过的事件不再触发
若跳转到新页面,则优先触发新页面探索
新页面事件触发完毕返回上一个页面
新页面事件未触发完毕返回上一个页面,则重新回到新页面。
第四条避免随机点到返回按钮的问题。
以下面模型图为例,我们介绍App的探索过程。
以A节点作为初始节点,从A节点的事件集合随机选择{e1, e2, e3}一个事件e1
进入B页面。遵循规则1,以B节点作为当前节点,随机从{e4, e5, e6}选择事件e4
停留B页面。遵循规则2,去掉e4事件,随机从{e5, e6}选择事件e5
返回A页面。遵循规则1,B页面优先于A页面触发,重新回到B。
遵循规则2,选择触发最后的e6事件
进入E页面。E页面触发事件e9
进入F页面。遵循规则3,返回上一个E;遵循规则3,返回B;遵循规则3,返回A。
至此页面B、E、F探索完毕。
流程简化为:
pb是 press back的缩写。
3.2 算法
算法的整体思想是采用递归的方式不断地在新状态下执行探索,直到探索完成。
算法说明:
算法输入是App的首页MainActivity,执行时间runningMin和一个访问过的事件集合visitedEvent
第1行:MainActivity 作为当前页面S
第2~3行:运行时间大于执行时间结束运行。
第4行:获取当前页面下所有有效的事件集合L
第5行:有效事件集合L减去访问事件集合visitedEvents得到剩余待触发事件集合L
第6行:若集合L为空,则跳转至第9行,否则执行第七行
第7~8行:从L随机选择一个事件触发,并记录触发后的新页面记做newState
第9行:根据新页面newState和旧页面S判断是否是返回事件,若是,则从上一个页面重新回到当前页面即第10行
第11行:将事件event加入访问事件集合visitedEvents
第14行:当前Activity页面作为当前页面S,重复3。
四、探索算法2.0
第三章提到的App模型是一种理想的模型,一种无状态的模型。事实上,App经常出现一个页面多种状态的问题。
4.1 App状态
在3.1节我们提到App的模型图是由页面和事件构成,节点代表页面,边代表事件。实际上,我们发现一个页面可能具有多种不同的状态。下面以Trip.com的机票搜索为例来举例。
Trip.com机票不同搜索结果
上图是Trip.com搜索不同目的机票的搜索结果。搜索热门城市香港到北京的机票,有数十趟航班,而搜索冷门城市马来西亚的BKI(亚庇国际机场)到巴哈马的ELH(北伊柳塞拉)的机票,却没有一趟航班。同一个搜索页面,搜索的输入不同,展示的结果不同。
App模型图无法表示具有状态的模型图,因此我们引入页面状态。
页面元素
引入页面状态之前,我们先定义页面元素。
页面元素是指页面上的一个个View组件,Android一般用resource-id 或者 content-desc表示。然而一个页面元素的resource-id可能相同(如列表),所以我们必须用一个能够唯一表示页面元素的方式。
我们想到了用xpath[3]来表示页面元素。
参考维基百科上xpath的定义:/A/B/C[1]/D[resource-id='value'] C节点必须是B的子节点(B/C),同时B节点必须是A的子节点(A/B),而A是这个XML文档的根节点。而D节点是C节点的第二个元素(C[1]),D节点的属性resource-id为value,[1]是称为节点的下标。
xpath是一种结合父元素、元素类型、元素id以及元素坐标的表示方法,能够精准定位一个元素。
酒店按钮的xpath是:
//FrameLayout[0]/FrameLayout[0]/FrameLayout[0]/TextView[@resouece-id="ctrip.english.debug:id/title"]
页面状态
页面状态是指页面所处的状态,我们用页面名称(Am)+所有页面元素(Xi)的集合来表示:
不同的搜索结果页,页面名称虽然相同,但页面元素完全不同。因此用页面状态可以区分不同的搜索场景。
页面事件
引入页面状态后,一个事件用三元组来表示E_i = <Am, Xi, An>,表示页面Am触发事件Xi进入An页面,其中Am称为源页面,An称为目标页面。
App状态模型图
引入App状态后,App模型图转变成App状态模型图。其中节点代表App的状态,边代表事件。
App状态模型图能够精准表示:在一个页面状态下触发某个事件,进入新的页面状态。
4.2 算法优化--相似元素
在介绍App状态模型图探索算法前,我们先小小优化第三章的算法。在第三章我们提出新页面事件触发完毕返回上一个页面。
这一条值得讨论。
在第三章,我们页面事件触发完毕的条件是所有事件都触发一遍。事实上真的如此吗?
以相册页面为例,相册页面事件数非常多,但所有事件对应一个功能(勾选)。如果用第三章的算法,随机从n张照片选择一张,直到所有照片都选择一遍,将耗费很长的测试时间。
人工测试遇到这种情况,一般采用取样+相似的思想:随机选择几个事件,测试OK。其他事件与样本事件相似,测试通过。
同样地,我们工具也引入取样+相似事件的概念。
事件相似定义
元素相似
当两个元素X_i和X_j除了下标外,其他内容完全相同,称为相似元素,记做Xi≈Xj。元素相似潜在含义是布局树中相同层级的元素可能存在相似的行为。
状态相似
当两个页面状态Si和Sj 页面名称相同,页面元素都相似时,称为相似状态,记做Si≈Sj。状态相似的潜在含义是同一个页面状态相同,其行为可能相似。
事件相似
当两个事件Ei = <Sm, Xi, Sn> 和Ej = <S'm, Xj, S'n>,具有以下特征时:
Sm≈S'm
Xi≈Xj
Sn≈S'n
这两个事件相似。事件相似的潜在含义是这两个事件完全具有相同的行为。
相似事件处理
一开始页面的相似事件是空集,随着事件的发生,具有相同行为的事件不断增多。当相似事件集合超过阈值时,我们认为剩余的相似元素全部相似,相似事件不再触发。
相似元素的目的是减少功能相似事件的重复触发的时间,探索更多的功能。
4.3 App状态模型探索算法
虽然App模型由页面模型改成状态模型,但本质仍然是是图的遍历问题,所以只要稍作修改即可得出App状态模型探索算法。
执行同上,不再赘述。
五、工具实现
由IAET基础服务和探索驱动两大模块组成。
基础服务模块
基础服务模块在保证保证探索正常运行的基础上,承担UI驱动的能力,主要由UI驱动和异常监控系统两部分组成。
UI驱动模块主要提供UI的检测和驱动、区分事件类型、计算页面状态和计算相似事件等能力。
异常监控系统主要保证App探索期间发生crash的情况下能够继续运行。
探索驱动
探索驱动主要将探索算法应用于实践,并结合一系列自定义规则来对App进行定制化的探索。
自定义规则
实际探索我们往往遇到各式各样定制化的需求,例如输入用户名和密码、不能进入某些页面、只想探索某几个页面。探索算法无法满足这类要求的,只能靠自定义规则来完成。
这些规则包括:预输入模块、黑名单模块、模块探索等等。
下面以预输入模块为例:
iaet-preinput.json
{"data": [{"activity": "activityname","list": [{"contentDesc": "contentdesc","resourceId": "resourceid","text": "value"},{"contentDesc": "contentdesc","resourceId": "resourceid","text": "value"},]},]
}
预输入模块解决在哪个页面向哪些元素写入什么内容的问题,主要解决登陆注册、用户输入页面的问题。
如 iaet-preinput.json 文件所示,你可以在某个页面,向 contentDesc或者resourceid 为 XXX 的元素写入YYY值,解决那些因输入校验而阻碍探索问题。
六、探索成果
上图是Trip.com最近7个版本,IAET在发版前发现的Crash数和每次达到的页面数。CrashNumber列是发现的Crash数,ActivityNumber列是1h运行达到的页面数。
Q:为什么表格中7.5.0前后Activity Number发生两次跃迁?
7.5.0前没有相似策略,经常停留在长列表页面。7.5.0引入相似元素策略后,解决长列表问题,增加了其他页面探索的机会。
7.5.1前RN页面作为单个页面统计,7.5.1统计一个个RN具体页面的名称。
Q:如果增加探索时间,Activity Number还会增加吗?
目前测试的瓶颈在于订单页面。因为大部分没被探索的页面都是支付订单相关页面,线上包很难通过正常途径进入。除去支付订单页面,目前的页面覆盖率在80%以上。
七、对比实验
此外,我们还选择16个国内主流App作为实验对象,以APE[1]作对比工具,运行1h,以Activity覆盖率作为衡量标准展开对比实验。
实验分析
我们选择近年来学术界最新的开源、效果好的自动化测试工具APE作为对比工具。
实验的所有benchmark均是国内主流App,涵盖衣、食、住、行、教育、娱乐、新闻、运动、知识付费等各个类别,App下载量均超1亿。
最后两列是本次实验IAET和APE的实验结果,IAET在大部分App上所达到的Activity覆盖率比APE高,且有几个大幅高于APE,证明我们工具的优势。
八、总结
自动化探索提供的其实是一种基础服务能力,为性能测试、Crash检测或者页面内容检测等不同的测试目的提供测试基础。
此外,自动化探索还可以在弱网、无网和低内存等极端场景下的进行测试,以验证App的健壮性和稳定性。
参考文献
[1] T. Gu et al., "Practical GUI Testing of Android Applications Via Model Abstraction and Refinement," 2019 IEEE/ACM 41st International Conference on Software Engineering (ICSE), Montreal, QC, Canada, 2019, pp. 269-280.
[2]Android上app_process启动java进程, https://blog.csdn.net/u010651541/article/details/53163542
[3] xpath,https://zh.wikipedia.org/wiki/XPath
【推荐阅读】
敏捷模式下携程的接口自动化平台演变
高效率低成本,携程流量回放平台实践
携程酒店DevOps测试实践
《携程架构实践》《携程人工智能实践》上市啦!
《携程架构实践》
京东
当当
《携程人工智能实践》
京东
当当
“携程技术”公众号
分享,交流,成长
这篇关于干货 | Trip.com 智能自动化探索测试的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!