2
头图

前言

提起 UI 自动化测试,总是会有人抛出很多疑问:

  1. UI 自动化能带来什么价值吗?还是在浪费时间?
  2. UI 自动化测试在整个测试流程中扮演什么样的角色?
  3. 有编写 UI 自动化测试的时间,我早就完成业务测试了,我为什么还要编写自动化测试的 case 呢?
  4. 针对于 UI 界面经常变动的业务场景,编写和维护自动化 Case 简直太难了,怎么样才能解决这些问题呢?

......

今天小编就在这里跟大家分享下,自己对 UI 自动化测试的理解以及我司质量平台正在搭建的分布式平台 DuLab 是怎样实现批量运行 UI 自动化测试 Case 的。

为什么要做 UI 自动化?

随着不停的版本迭代,软件新增功能变的越来越多,对测试资源的需求也变得越来越大,执行人工测试的时间越来越长。对于人工测试的依赖开始变得棘手,因此大家开始寻找解决方案,UI 自动化也应运而生。

人工测试的弊端

  • 人工回归测试需要花费很长时间才能完成,很小的延迟就会让发布面临风险。
  • 发布节奏受到人工回归测试的限制。两天以上的人工回归测试意味着最好的情况下能够一个月发布两次。而且,开发者需要一次性发布所有东西。要么全部发布,要么什么都发布不了,因为需要将所有东西一起测试。

UI 自动化测试的优点

  • 解放了测试团队针对临时的和探索性案例的测试时间;
  • 可以一边开发一边进行回归测试,减少等待时间;
  • 可重复性使用,快速进行回归测试;
  • 更好的利用资源(周未/晚上的资源空闲时段)。

UI 自动化的特点

UI 即 User Interface(用户界面)的简称,UI 自动化做的事情就是模拟用户行为进行操作,完成对用户界面的测试。这也就从本质上限制了它的使用场景:

  • 软件需求变动不频繁
  • 产品更新维护周期长
  • 比较频繁的回归测试
  • 自动化测试脚本可重复使用

所以在你开始之前,最好认识清楚哪些业务场景是可以自动化的~

预期效果

针对我司的业务现状,确定好预期效果。

  • 兼容性测试:针对市场上常用机型与系统版本,进行下载安装使用,以发现兼容性故障,进而修复。
  • 埋点测试:校验埋点数据是否正常上报,有无漏报,错报,多报。
  • 回归测试:版本迭代中,进行回归测试保障代码改动不会导致其他场景产生故障。
  • 测试阶段性能收集:在测试阶段为自动化 case 指定优先级,按照优先级运行自动化 case,提供更多的性能数据。

思路

与接口自动化测试思路相同,我们在进行 UI 自动化测试时,每个 Case 都是一个单独的 TestCase,我们将所有需要执行的 case 放在同一个 TestSuit 中,批量执行并生成聚合报告。

难点

  • 不同于接口自动化,单个客户端设备某个时刻仅支持运行一个自动化 Case;
  • 受制于电脑性能,无法在单一电脑设备上同时控制数十台移动设备;
  • 随着版本迭代,部分 Case 仅适用于某些 APP 版本,需限制版本范围;
  • 无法省略前置步骤,比方说想要对某个页面进行 UI 自动化,无法直接进入该页面,要从启动 APP 开始,选择前置路径才能进入指定页面;
  • 相同场景下数据可能会发生变化,使得校验规则无法统一;
  • 不同账号下,数据不同,可能会导致很多场景无法测试;
  • 对各种系统各种型号的移动设备,进行远程控制;
  • 如何在 UI 自动化的过程中校验埋点数据;
  • 大量的 AB 实验,如何切换环境进行测试;
  • APP 安装包的管理,如何选择安装包进行覆盖安装.
  • ......

架构

下面是小编自己从点到面一步步的思考历程,从基本的移动设备远程管理,case 编写到 case 的维护,再到 lab 平台的搭建:

主流工具调研

目前市面上有很多成熟的 UI 自动化工具,如appium,airtest,Sikuli等等,它们提供了非常便捷的录制服务,我们只需要在 idea 上拖拖拽拽,马上就可以生成一个简单的 UI 自动化脚本,且我们只需要将这些工具封装的工具库部署在我们的电脑上就可以多次运行我们编写的 case 了。

这里我们将分开剖析进行 UI 自动化用到的录制器和执行器的原理:

case 执行的原理

解读其源码我们发现这些工具其实底层的实现原理都基本相同,核心即为:对移动设备的远程控制,并将控制指令封装为可读通用的语法。

其实我们日常使用手机时,手机系统就是通过我们点击的屏幕坐标,从最上层的 view 依次向下遍历,直到找到该坐标位置下可以响应用户行为的 UI 控件,执行对应的响应代码。

但是我们在编写 case 时,如果 case 中记录的全是在某个坐标下对应的操作行为的话,就会特别的晦涩难懂,且针对不同屏幕尺寸的设备也完全不通用,这显然是行不通的。

真实的用户行为其实无非就是,点击了某个按钮,滑动了某个页面等等,主体其实就是 UI 控件,我们在编写 case 时也是一样的,我希望的是对某个 UI 控件进行操作,那么其实在执行 case 时,我们完全可以获取该 UI 控件在当前屏幕中的位置,再去调用底层的控制指令进行操作。

case 录制的原理

其实 case 录制的原理就是上述执行原理的逆向思维,我们可以在移动设备上开启 server 服务,将移动设备的屏幕影像通过二进制流的形式实时传输给录制器,将移动设备投屏到我们的录制器上。同时还需要实时获取移动设备上的 UI Tree ,当我们在录制器上进行鼠标移动时,利用鼠标在屏幕上的坐标信息从 UI tree 中遍历对应的 UI 元素,并打印该元素的所有 UI 信息。

是不是感觉很复杂,不用担心,已经有成熟的框架 Android Debug Bridge 和 WebDriverAgent 为我们提供了这些服务,下面👇会重点介绍的。

与移动设备进行通信的框架

有兴趣的同学可以自行 google。

这里我们直接选用网易的AirtestProject作为我们 UI 自动化的核心框架,原因:

  1. 提供了现成的脚本录制工具 Airtest IDE;
  2. 除了通过 path 定位元素外,还参考Sikuli支持图像识别定位 UI 元素;
  3. 相比于 Appium 移除了对 Java,JavaScript、Objective C、Java、Ruby、php、c#等语言的支持,更加轻量级;
  4. 提供了Airtest和Poco框架可以在 python 项目中直接引用,也支持命令行一键执行;
  5. 提供了更加直观的 report 报告。

Case 管理

解决了 case 的录制问题,下面我们再来思考 case 的管理问题,在录制 case 的过程中我们发现很多 case 都存在高度可复用模块,如登录模块,可能我们 80%的 case 中都进行了登录操作,若某个版本中登录页面的 UI 发生了改动,导致元素的 path 发生了改变,这个时候我们难道要找到所有设计到登录的 case,逐个进行修改吗?

其实在编写 UI 自动化 case 时,我们同样也需要设计模式,参考业界优秀模式,再结合我司实际场景,整理的大体思路入下:
上传![

基础层

  • 常用操作封装:抽离重复代码,进行封装;
  • 工具类封装:数据校验,设备信息获取,安装包数据查询,按照包下载等操作;
  • 埋点遍历封装:以单个页面为单位,获取所有可点击,可滑动,可编辑的 UI 元素,再模拟相应的用户行为;
  • 应用程序安装卸载封装:根据 udid 获取各版本对应的安装包,进行覆盖安装,或卸载安装;
  • 系统设置封装:切换环境,ab 实验,网络环境;
  • 版本选择封装:指定 case 运行的版本区间。

业务层

  • 页面封装:按照功能划分,如评论相关操作,登录相关操作等,将这些通用部分抽离并封装,供所有测试 case 调用;
  • 模块封装:进入某个业务模块的前置路基封装等。

用例层

  • 测试用例集:按照业务分子文件夹,包含该业务的所有测试用例。

框架层

  • suit:存放用于组织不同用例集的 Suit 类;
  • report:生成单个 case 的 report 报告,和整个 suit 的聚合报告。

APP 安装包管理

接入发布平台,定时轮训获取提测后的测试包和灰度包信息并落库。 表结构如下:

CREATE TABLE `lab_package` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `version` varchar(32) CHARACTER SET utf8mb4 DEFAULT '' COMMENT '版本号',
  `buildVersion` varchar(32) CHARACTER SET utf8mb4 DEFAULT '' COMMENT '构建版本号',
  `bundleId` varchar(255) CHARACTER SET utf8mb4 DEFAULT '' COMMENT '包ID',
  `pkgPath` varchar(255) CHARACTER SET utf8mb4 DEFAULT '' COMMENT '包存储路径,可以用来下载包体',
  `platform` varchar(255) DEFAULT NULL COMMENT '平台 ios/android',
  `timestamp` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `branch` varchar(255) DEFAULT NULL COMMENT ' 所属分支',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=579001 DEFAULT CHARSET=utf8;

在 case 指定版本范围时,从数据库中查询最新的符合要求的按照包地址(iOS 端存在多个不同 bundleId 的安装包,需要先根据设备的 udid 获取正确的 bundleId),然后下载安装包并安装到运行的设备上。

接入 mock 平台

UI 自动化 case 运行时,最大的困扰是 UI 界面的变动。但是如果我们接入 mock 平台,保障 case 运行时的界面和 case 编写时的界面以及数据是完全相同的,那么我们在执行 case 时校验验证点,将会变的轻而易举。

分布式

前面我们准备工作做好以后,就要考虑 case 运行的问题了。不同于接口自动化,UI 自动化依赖于真实的设备,但是设备资源是有限的,我们在一台 Mac 上运行时,根据 Mac 的性能,最多可以同时控制几台设备,但是当我们希望批量运行 case 时,可能需要在几十台甚至几百台设备上同时运行我们的 UI 自动化 case。此时我们就需要分布式了,搭建一个手机 lab 集群,由一台 server 分配任务,多台 worker 执行任务,远程控制连接在该 worker 上的移动设备,最后再将所有的 report 报告进行聚合。

lab 框架

语言:python

核心框架:tornado+celery+redis

整体架构:

lab 主要分两个模块,server 调度服务,以及 worker 集群。当 server 服务接收到用户执行指令时,会去 case 仓库遍历符合用户要求的 case 脚本,由 broker 中间件将任务分别分配给 worker 集群,各 worker 任务执行结束后,会将每个 case 的执行结果存储到 result 结果集之中,待任务全部执行结束 server 会生成总的聚合报告,并将 report 报告通过飞书发送给用户。

其中 worker 的整体架构如下:

当 worker 收到任务后,会开启多进程在连接的移动设备上批量执行 UI 自动化 case,

值得一提的是,worker 中本地资源的管理,分别是对安装包的管理和对 case 仓库的管理;

case 仓库其实是 lab 项目的子模块,测试人员在日常 case 的编写和维护都是在 case 仓库的项目中完成的,完全不用关心 lab 项目的维护;当有测试同学 push 代码到远程仓库时,Jenkins 就会调用 server 服务的接口,由 server 服务通知各个 worker 更新 case 仓库的代码即可,保证 worker 在批量运行自动化 case 时,代码是最新的;

安装包的管理:有两种形式,worker 服务除了每天定时获取最新的安装包列表缓存到本地外,当 case 执行时若本地安装包无法满足版本要求,worker 也会遍历 app 安装包的数据库,将适合的安装包缓存在本地并安装到移动设备中;

因为我们的 app 每天都在进行着很多的版本迭代,相应的我们的 case 可能只能在部分版本区间中运行,那么我们此时就需要在 case 中限制版本区间,当 case 运行时,判读设备中已安装的 app 版本是否在该区间内,若不存在则下载安装满足要求的版本到移动设备中再去执行 case;

更多更细节的设计这里就不再介绍了,有兴趣的同学留言探讨哦~

文|crystal

关注得物技术,携手走向技术的云端


得物技术
846 声望1.5k 粉丝