爱奇艺 iOS 稳定性测试实践
稳定性测试是长时间持续运行 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 特性,我们封装的返回逻辑是,大部分页面可左滑返回上一页,禁用左滑的页面则靠以下方式来处理。
比如 H5 页面的关闭导航按钮等,可识别 DOM 树中属性包含 back/close 字样的元素为返回、关闭按钮,尝试点击以返回上一页;
部分页面导航按钮以“返回”文字呈现,方法 1 就找不到这种退出按钮。我们把屏幕截图上传给 OCR 文字识别服务,识别屏幕中的“返回”文字。点击识别到的返回文字以尝试返回上一页;
部分广告浮层的关闭按钮是“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 发现效率,对历史上更容易出现崩溃的页面增加遍历权重等等。
评论