前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >干货 | Trip.com 智能自动化探索测试

干货 | Trip.com 智能自动化探索测试

作者头像
携程技术
发布2020-06-01 16:24:03
1.3K0
发布2020-06-01 16:24:03
举报
文章被收录于专栏:携程技术携程技术

作者简介

祥星,携程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获取当前页面所有点击元素:

代码语言:javascript
复制
// 递归获取当前节点所有可点击的子节点
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桥接类。

代码语言:javascript
复制
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。

代码语言:javascript
复制
public static void main(String[] args) {
   UiTestAutomationBridge.getInstance().connect();
   AccessibilityNodeInfo rootNode = UiTestAutomationBridge.getInstance().getRootInActiveWindow();
}

第三步将JAVA文件打成jar包,使用app_process运行jar包。

代码语言:javascript
复制
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是:

代码语言:javascript
复制
//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

代码语言:javascript
复制
{
  "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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 携程技术中心 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、简介
  • 二、UI驱动
    • 2.1 UiAutomator简介
      • 2.2 利用UIAutomator查找所有可点击元素
        • 2.3 运行
        • 第一步,创建UIAutomaion桥接类。
        • 第二步 创建一个Main函数调用connect方法,获取RootNode。
          • 三、探索算法1.0
            • 3.1 App模型图
            • 3.2 算法
          • 四、探索算法2.0
            • 4.1 App状态
            • 4.2 算法优化--相似元素
            • 4.3 App状态模型探索算法
          • 五、工具实现
            • 基础服务模块
            • 探索驱动
          • 六、探索成果
            • 七、对比实验
              • 八、总结
                • 参考文献
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档