爱奇艺iOS稳定性测试实践

846 阅读13分钟

稳定性测试是长时间持续运行APP,以验证应用是否稳定的测试。它可以有效发现APP长时间运行下的偶发闪退、内存泄露、性能变差等问题。iOS端通常由苹果系统的API快速执行点击事件,开展稳定性测试,类似的优秀工具如FastMonkey等虽然有诸多好处,但是作为长期运行的测试服务系统,还需要调整功能以适应企业级测试场景,无法解决通过外部请求定制事件执行序列、无法动态设置启动参数、截图存在本地将导致磁盘占用过大等问题。

爱奇艺测试团队在iOS稳定性测试方面展开了不懈的探索,也积累了一些相关经验,希望借本文跟大家分享在【iOS稳定性测试】实践和优化过程中的心得和体会,也借此机会抛砖引玉,征求同行的更多探讨。

一、方案实践

1.1基础框架

爱奇艺iOS稳定性测试基于现有的云真机体系,总体分为三大块。向下是设备管理,向上是产出物汇总,核心是测试策略。基础框架如下图1,真机设备通过驱动层接入远程控制体系,统一由后端调度管理,核心策略通过设备驱动与真机设备交互,模拟用户行为开展测试,测试过程中产生的数据统一汇总到后端,由后端生成测试报告并反馈给用户。

                                             图1. 系统框架

系统结构如上,下面重点讨论策略选择的问题。

常用的稳定性测试方案,有Monkey测试、录制回放或者按元素遍历的方式等。其中Monkey测试实现成本较低,但有的页面元素较少,盲目的操作经常点不到元素,导致测试效率较低;对于长期执行录制回放的方案,其执行路径固定且与业务功能耦合,可能需要长期维护业务逻辑,不利于各业务线快速拓展稳定性测试场景;而按元素遍历操作的方案,可以较好的处理上述方案的问题,但是实现成本比Monkey策略高,需要测试团队持续投入研发力量。

我们尝试了随机滑动点击策略、按元素点击的遍历策略,下面分析下两种方案的实现细节和难点。

1.2生成测试事件

我们抽象出一个模块叫事件生成器,主要作用是持续产生用户事件流作用于APP,以达到验证APP稳定性的目的。本文重点介绍随机产生和根据页面元素产生两种模式。

1.2.1随机产生事件

随机事件方案的典型代表是Monkey,它向系统发送随机的用户事件流(如点击、输入、滑动等),实现对正在开发的应用程序进行稳定性测试。该方案通过XCTest提供的接口就可以快速模拟相关操作,实现成本相对较低。早期为了快速打通测试流程,验证方案有效性,我们实现了Monkey测试的方案。如图2所示:先获取本次任务期望各事件发生的占比配置(涉及重启APP、按home键、切换横竖屏、点击事件、滑动事件、后退等事件),再按概率指定本次执行哪种事件。组装好需要执行的事件,最后请求具体的驱动服务以执行事件。

                                 图2 随机事件生成器

1.2.2根据页面元素产生事件

随机策略虽然点击频率高,但是无效事件占比高达90%。分析测试报告发现当页面可点击元素较少时,随机生成器产生的事件就会触发大量无效操作。如果可以根据页面的可操作元素进行遍历操作,就可以大大减少无效事件的产生。因此尝试使用按元素点击的策略,执行深度/广度优先遍历策略,提升测试效率。

那iOS端如何获取元素、记录执行路径以及按元素来遍历的方式执行测试策略呢?

  • 识别页面上的元素

按元素点击首先要解决元素识别的问题。

方法1,被测APP集成SDK,接入成本高,且正式包一般不允许带入;

方法2,从DOM树解析元素,存在DOM树元素累积的情况,当页面分页时,无法区分仅当前页的元素,影响元素解析准确性。

这两种方法都存在一定的问题,因此我们把眼光转向了方法3,AI图像识别元素。AI图像识别作为一项相对成熟的技术可以有效避免上述问题,并提供相对准确的元素数据。因此在元素生成方面,我们借助爱奇艺内部提供的AI服务,在事件操作前将画面截屏传递给AI服务,快速识别绝大多数元素区域,如下图3所示。

图3 AI服务识别页面的元素

  • 对多个页面去重,定位当前所处页面

解决了元素识别的问题后,下一步就是解决元素遍历的逻辑。为提高遍历效率,遍历逻辑需要尽可能的避免多次进到重复的页面遍历。为了定位到当前所处页面以记录执行路径,顺带避免相同页面重复请求AI服务,需要对当前页面是否曾经进入过进行判断,我们考虑的方法有2个:

方法1,从DOM树提取关键信息来生成指纹,以指纹定位当前所处的页面。但经过实践发现获取DOM树的接口耗时不稳定,经常耗时>4s,不利于快速定位页面。

方法2,利用事件操作前后的屏幕截图生成像素指纹,来定位当前所处的页面。实践发现通过此方法定位页面,速度可达到毫秒级别。

大致流程如图4,先获取屏幕截图,再把截图处理灰度化、缩放为8*9的缩略图,最后再生成包含图像信息的指纹。通过计算指纹之间相似度是否超过阈值来判定图片是否为同一张,从而判断处在哪一页。优点是后续可直接通过指纹计算页面之间的相似度,速度较快,也通过提高阈值避免局部细微变化干扰判断结果。

图4 以屏幕截图生成指纹判断相似度

  • 返回上一个页面

广度优先算法每个步骤点击成功都需要返回上页,继续点击该页其他元素;深度策略点到没元素点击的页面也需要尝试返回以继续点击,所以实现通用返回策略是有必要的。不同于Android有物理按键强制返回,iOS端需要自己来实现一套返回方法。结合被测APP特性,我们封装的返回逻辑是,大部分页面可左滑返回上一页,禁用左滑的页面则靠以下方式来处理。

  1. 比如H5页面的关闭导航按钮等,可识别DOM树中属性包含back/close字样的元素为返回、关闭按钮,尝试点击以返回上一页;

  2. 部分页面导航按钮以“返回”文字呈现,方法1就找不到这种退出按钮。我们把屏幕截图上传给OCR文字识别服务,识别屏幕中的“返回”文字。点击识别到的返回文字以尝试返回上一页;

  3. 部分广告浮层的关闭按钮是“X”图样的关闭图片,上述两方法都不能处理。我们则批量搜集此类关闭图样训练了可以识别关闭按钮位置的AI工具,通过点击识别的元素位置以返回上一页。

所以我们如示意图5,按元素深度优先遍历策略单步事件执行。

                                        图5 元素事件生成器

1.3处理运行时的突发干扰

实践发现,自动化测试运行时可能存在偶发的弹窗阻挡点击目标元素、可能误操作使APP退到了后台,也可能在某页卡住无法退出。如何处理各种阻碍测试的异常,确保APP测试过程中可以触达更多的页面,提升测试覆盖程度呢 ?

1.3.1部分应用进不去首页的解决

有的APP必需先执行登录等操作才能进入首页开始测试任务。各个业务线情况多种多样,所以框架支持定制化脚本的运行。允许让用户定制差异化的前置场景,在运行稳定性测试前先执行定制脚本,再开始稳定性测试。

1.3.2偶然的弹窗面板处理

APP运行过程中出现的弹窗多种多样,大致可分为如下表格6中几类:

表格6 弹窗情况分类

系统弹窗检测和处理相对简单,只需要提前设置要识别的文本,WDA接口通过文本属性判断元素存在则直接点击掉。但是当业务线越来越多,需要设置的文本数量也急剧增长,每步事件操作前都需要循环判断是否有满足设定文本的弹窗,时间损耗太大。

研究发现iOS的“正则表达式”-NSPredicate方式,支持一次请求判断多个元素是否存在,同时正则表达式的方式可以根据需求快速扩展,有效的避免文本的穷尽枚举。基于这样的新的思路,每步检测弹窗只需要请求WDA一次,即可判断N种文本,减少了N-1次网络请求耗时,提升执行效率。

例如图7:当出现以下弹框时“label='取消'or label='同意并继续' or label='我知道了'” ,一次请求即可判断多种弹窗文本是否存在,存在则逐一处理掉。

                                                         图7 各种弹窗情况

1.3.3保持被测应用处在前台

APP在操作过程中,可能因为某些操作跳出当前APP,因此保证当前被测APP一直在前台运行是十分有必要的。常见的方案有两种

方法一,直接分析当前所处页面DOM树,获取XPath返回的XCUIElementTypeApplication属性,此属性值与被测APP名称一致则认为被测APP保持在最前台。但是当被测应用页面复杂时,XPath查询的速度会变慢,严重影响任务执行的单步耗时。

方法二,利用苹果官方提供的接口activeApplication可直接输出当时前台应用的Bundle ID,返回的Bundle ID与被测应用一致就可以快速确认当前APP是否在前台。这样不再查询页面整体的XPath信息,避免返回过多的无用信息,也更稳定、快速。

不过方法二也不能处理所有的情况,比如当手机打开了辅助功能(小白点)的面板、或者存在左上角的跳转返回其他应用按钮时,方法2会误判顶层APP为非被测APP。判断下发的事件是否有效执行

稳定性任务运行过程中,如果遇到静态页面,在点击退出之前,当前页面触发的操作对于APP而言都是无价值的,我们称之为无效点击,为了尽量减少无效点击的数量,我们希望在操作事件之后进行判断,最直接的想法就是判断操作之后页面是否发生了变化。因此需要在事件操作前后进行截图,将截图直接生成均值hash的指纹作为图像的标记,连续2步骤之间的指纹汉明距离小于阈值则判断为同一页面 (图8)。连续2步骤相同则判定这个事件无效。

                                           图8 判断屏幕截图的相似度

解决了测试事件的生成、处理好突发的情况,就可以按下述图9 服务框架循环执行测试服务。

                                      图9 服务框架

二、落地应用

为了验证不同策略下的效果,我们进行了对比实验,每种策略单独运行20次,每次连续执行8小时,选取了事件总数、无效占比、单步耗时和页面覆盖数4个指标进行评价。对比数据见表10。

                                                    表10 各策略数据对比表格

在页面覆盖数方面,由于iOS端无法获取类似Android的Activity个数来统计,我们使用近似的数据来代替,利用测试过程中的事件截图,对截图去重来近似衡量页面覆盖数。

页面覆盖数计算规则:每个图片计算指纹,图片之间通过指纹计算相似度,相似度过高则只计一页;累计所有的截图数量认为是此次任务的页面覆盖数。

下图11中,X轴是测试执行时长,Y轴是画面覆盖数。可以看到8小时执行后新的按元素遍历策略覆盖率比随机点击策略提升了将近30%。

 图11 各策略覆盖的画面数随时间增长趋势图

从几组数据来看,随机点击的平均单步耗时短、点击频率高,但是有效时间占比偏低。元素遍历策略在有效事件占比和页面覆盖数方面提升明显,但由于需要大量切图等操作,单步耗时较高。两种方式各有利弊,可以根据实际的测试场景进行选择,如果希望验证APP的抗压性可以选择随机策略,如果希望覆盖到更多的页面则可以选择元素遍历策略。

2.1独立APP运行

当前两种测试均在爱奇艺内使用,稳定性测试任务每月运行800+次,发现崩溃200+,在整个 DevOps 流程中扮演重要的基础服务角色。

2.2模块指定页面执行

除了在独立APP中运行,也有团队需要在指定的模块页面内开展稳定性测试,我们尝试在被测页面DOM树加入隐藏标识,周期性判断标识是否跳出了限定范围。该方案基本满足业务场景,可持续在限制页面范围内执行测试。但对程序也有一定侵入性,需要开发在页面增加定制化的属性,供测试工具判断是否跳出限定范围。

三、后续优化

当前的稳定性测试策略都是蛮力点击和遍历,后续会拓展更多的更有针对性的遍历策略,比如追求用户实际操作情况的一致,尝试根据线上用户流量提取用户行为,生成更加切合用户实际的操作行为;追求bug发现效率,对历史上更容易出现崩溃的页面增加遍历权重等等。