主题测试自动化:Fastlane篇

本文算是 “从视觉到 App:网易有钱 iOS 项目主题自动化方案” [1] 的补充。 行文的主要目的是展示如何用 fastlane 工具,做多主题在多设备上的测试任务,所以不会对 fastlane 做全面系统的介绍。但如果在自动化任务中用到 fastlane 相关技术点,重要的细节、坑或者最佳实践,会在相应的步骤特别的说明。

需求

有钱的 iOS2.7.5 版本增加了主题功能,用户可以选择自己喜欢主题。一期上线的主题数量有 7 种,在制作过程中,发现需要适配带 P 的 iPhone(指 6P,6SP,7P)、iPhone6、其他 iPhone 等三类设备(三类设备的图片素材是不一样的)。所以我们需要对 7 * 3 = 21 种情况来检查皮肤是否显示正常(暂时不考虑多语言)。

如果由 QA 手动测试,除了需要切换不同主题外,还需要切换三类设备;加上主题的配色其实还在迭代中,一旦改动又需要重复的走一遍流程,这个流程对 QA 而言机械重复而无趣。

所以,我们的目标是如何去解决人工手动测试,费时费力等问题,让大部分工作可以自动化。

1. Fastlane 概述

Fastlane 作者 Felix Krause,于 2014 年创造了 fastlane 项目,一年后的 2015 年被 fabric 收购,在此之前的 2014 年,fabric 被 Crashlytics 收购,而 Twitter 则在 2013 年收购了 Crashlytics。自 2015 起,作者一直在 Twitter 开发 fastlane,直到 2017 年 Google 收购了 fabric,现在在 Google 全职开发 fastlane。 从履历上看,fastlane 的背后有强大的公司支持和实践,近年来发展势头很好,更新迭代很快。(在前段时间 iTunes connect 更改了后台登录时验证方式之后,fastlane 团队第一时间获取信息,发布了公告,并在一周内 fix)。

fastlane 内置了很多常用的 actions,如 snapshot、frameit、deliver 等,实现了诸如截图、定制 Appstore 使用的截图,自动上传 APP 到 iTunes connect 等功能,满足大部分场景下的需求。更全更详细用法可在 在线文档 [2] 查阅;事实上,我们可以自己编写 actions,来满足自身的业务需求

可定制化 + 活跃的社区 是所有框架,包括 fastlane 在内,流行的要素之一,fastlane 目前被官方文档记录在册的 actions 有 185 个(运行 fastlane actions 可以看到底下的 Total of 185 actions),不断有新的 actions 被吸纳到默认 actions 里。今年 4 月份以来,fastlane 整理发布了诸多的 plugins(目前有 173 个,运行 fastlane search_plugins 统计下),期望达到像 npm 的 node_modules 那样在程序员之间的代码共享的目的。

Fastlane 本质上是一系列符合 fastlane 规范工具的执行环境。这些工具处于 fastlane 的管理之下,可以使用 fastlane 的全局变量、credentials_manager、上游 action 的输出等数据。fastlane 封装了编写 action 时,经常使用的组件或 util,使每个 action 的行为表现一致。如在控制台输出错误提示信息时,可以使用以下语法

puts “Must name a themeServerId”.red
// 或者
UI.error(“Must name a themeServerId”)

每个 lane 执行完毕,在控制台都会有相应的统计数据,样式也完全一致。fastlane 是 ruby 的一个 gem——为了定制自己的 actions,你需要了解 ruby 的基本使用。

如果需要了解关于 fastlane 详细的信息,请访问官网在线文档,Getting started with fastlane for iOS [2]

2. 主题测试自动化步骤

2.1 安装 fastlane

确保安装了 ruby,版本在 2.00 以上。假设你已经成功安装 fastlane。

注:如果安装过程里出现了错误,fastlane 会在输出错误信息的同时,很智能的在 open 或者 closed 的 github 的 issues 查找相关的解决方案,输出在 console 里,供我们 fix 时做参考

之所以,fastlane 能够智能的提供错误解决方案,得益于 fastlane 对 github 上 issues 的良好管理,当向 fastlane 的 github 工程里提 issue 时候,github 的机器人 fastlane-bot 会提示你请提供 fastlane env 在本机上的输出结果。这是个非常非常聪明的作法,利用众包的方式发现问题、解决问题。类似利用众包这种技巧的,例如李飞飞的 ImageNet,它的诞生也离不开众包,进行有监督的学习,参见 从 GPU 到 ImageNet,两位硅谷华人改变了 AI 发展史 | 总编专栏 [3]。

2.2 初始化 fastlane

.xcodeproj 文件所在的文件夹下执行

fastlane init

会在当前目录下生成一个 /fastlane 的文件夹。此后所有的 fastlane 相关文件都会在此文件夹下。

本流程不需要 app_identifier、apple_id、team_id 的数据,所以不需要 Appfile,此处略过。 在生成的文件里,其中最重要的是 Fastfile。打开 Fastfile 文件,定义自动化测试的任务,起名叫 test_ui_test

为什么是这个名字呢?第一个 test 表明是测试阶段的命令,剩下的是 UITest 名字,在 ruby 语法里的写法——when in Rome , do as the Romans do

基础代码如下,

lane :test_ui_test do
…
snapshot(
reinstall_app: true,
skip_open_summary: true,
clear_previous_screenshots: false,
devices: devices
)
…
end

有了入口文件,参考 fastlane 的文档接口,调用合适的 actions,如 snapshot 等内部 action,结合普通的 ruby 语句,我们就可以开始实现自动化截图任务逻辑了。

2.3 主题测试后自动化方案

1. 需求说明 已知需求是: 多设备适配 + 多主题测试。

2. 思路 多设备适配,可以通过指定 snapshot 的配置参数 devices = [“iPhone 5”, “iPhone 6”, “iPad Air”] 来实现。那如何定义多主题测试呢?总不能默认执行所有皮肤吧——我们需要开始运行时,指定本次测试需要测试哪些皮肤。根据项目情况,决定用主题的服务端配置的 id 来标示(themeServerId)需要测试哪个皮肤。themeServerId 有一个对应关系。如下表格

主题serverId 主题名称
001 粉红物语
002 月宫玉兔
003 海底世界
004 缤纷几何
005 宁静夏夜

最终方案是:在执行 fastlane 命令的时候去指定主题 id,可以指定多个主题,这样就可以实现测试多个主题;同时指定多个设备,会有默认设备。需要额外处理,如何在命令行传参数给有钱的 APP 的问题。

为什么会采用这种方案?

2.1 有钱的主题的更新机制。 有钱的主题是通过用户在界面选择主题类型,触发代码

[[ThemeCacheManager sharedManager] downloadThemeWithServerId:serverId]

来实现皮肤的下载。 2.2 fastlane 工具链流的限制。

在输入了命令 fastlane test_ui_test 后,fastlane 去调用 XCUITest,然后 XCUITest 启动有钱的 APP(重要的一点,XCUITest 只是对有钱 APP 的代理),在 XCUITest 运行阶段,只有受限的接口去和 APP 交互;运行前,更不能直接调用 ThemeCacheManager 的方法。 而且 fastlane 和 XCUITest 之间也无法通讯。

2.3 在翻阅了大量 fastlane 相关资料,、XCUITest 相关 API 资料、启动 APP 相关资料,发现 snapshot 的参数里有个宝贝——launch_arguments 参数.

snapshot(
skip_open_summary: true,
clear_previous_screenshots: false,
launch_arguments: [args],
devices: devices
)

snapshot 的 configuration file 即 SnapshotHelper.swift 获取到 snapshot 注入的 launch_arguments 参数,并且在 XCUITest 启动的时候设置 APP 的启动 launch_arguments。然而 XCUITest 却拿不到这个参数。但是通过 XCUITest 启动的 APP,却是可以拿到的——是 Session 级别的 NSUserDefaults 数据。使用下列的语句获取传入的 themeServerId。

[[NSUserDefaults standardUserDefaults] objectForKey:@”themeServerId”]

在 APP 的首页拿到这个参数,那么就可以在内部使用这个参数来调用下载皮肤的接口,

[[ThemeCacheManager sharedManager] downloadThemeWithServerId:serverId]

注意:在 XCUITest 里需要处理下载皮肤的等待。

OK,从 fastlane 的内置 action,snapshot 传值给有钱 APP 的路走通了,如何在 fastlane 的命令行,传值给 snapshot 的调用参数呢?比如,我们输入 themeServerId 变量是终端里,而不是去每次去修改 Fastfile 里的参数 launch_arguments

经过一番搜寻,在 fastlane.tools 的 advanced.md 模块找到了答案。fastlane/Advanced.md at master · fastlane/fastlane · GitHub [4]。形如,

fastlane test_ui_test themeServerId:004

就可以传值给 snapshot 作为调用参数。

2.3 实现 test_ui_test 任务逻辑(lane)

1 编写 Fastfile 文件 。 在 lane :test_ui_test 里编写逻辑——获取 themeServerId 和需要测试设备类型等 2 个参数,然后将获取的参数传入内置 action 之 snapshot 的 launch_arguments,最后是打开截图的 summary 界面的逻辑,相关代码略过。

2 编写截图的逻辑。UITests.swift 文件里编写截图代码。包括截图内容、截图顺序。

良好配置的测试用例,可以减少测试次数,优化测试方案,节省时间

按照需求,需要截取 6 个关键界面,但前提是需要先下载到某个主题——解决方案:可以通过触发下载后,等 18s 左右来实现。 另外,在进入第一个关键页面,即首页前,可能会有新手引导的一系列界面出现,所以需要排除掉新手引导的干扰。

相关代码,可以通过 XCUITest 提供的录制功能修改而来,此处略过。

3 对截图的分组展示和区分。 截图完成之后,会默认打开截图总览界面,你会发现多次截图都混着一起,浏览的交互方式也很差劲,看起来很不方便,如图;

所以需要一个更好的交互方式、界面组织方式,经过一番思索。对现有的结构做如下改进

  1. 每一个主题每一个设备的截图为一组,而且一组的展示不需要左右滑动。需要查看所有图片的时候,可以弹窗展示,不需要横向纵向滚动条。
  2. 多次截图,按照先后顺序,向下排列,避免出现左右滚动条。
  3. 当皮肤数量很多时,可以在纵向扩展,通过竖向滚动,即可浏览全部截图。

这就意味着需要自己定制。所以,决定开发一个 action 实现上述功能。按照官网的步骤,编写 resort_screenshot 的 action,这个后面会详细讲到逻辑实现。这里只需要明白一点:

resort_screenshot 对 snapshot 的截图汇总界面和图片素材做了二次处理

而实现的,并且保留了 snapshot 原生的汇总界面。所以在 lane :test_ui_test 里,执行截图命令之后,接着执行如下代码,

resort_snapshot(skip_open_summary: skipOpenSummary)

其中 skipOpenSummary 表示是否不需要自动打开截图的结果页面。这个选项在测试机上是 YES,因为测试是执行完毕之后,会返回一个 url,供 QA 点击,在 QA 本地浏览器打开——并不需要在测试机上打开。

3. resort_screenshot 的 action 编写

我们编写的是 action,而不是 plugins。这里需要特殊说明下 action、plugin 和 fastlane 的关系。 Action 是 fastlane 里基础的逻辑代码,完成一个特定任务,可以在各个 lane 的定义里使用,但是只能在同一个文件系统上的工程之间使用;而 plugin 则是对 action 的封装,确定当前 action 可以依赖的其他 plugin 的 action。功能纯粹,代码良好的 plugin 可以发布到 github 之类的地方,供别人在自己的 Fastfile 里使用。打个比方,

plugin 是 npm 系统里的一个 node_module,而 action,可能是 node_module 里的一个 JS 文件,是单纯的代码的 package

3.1 如何编写 action 在命令行输入

fastlane new_action

接着输入 action 的名字,这里我们输入 resort_screenshot,然后会生成 /fastlane/actions/resort_screenshot.rb 打开这个文件,在对应的模板处编写逻辑。

因为 resort_screenshot 是对 snapshot 原来的 screenshot 目录的二次处理,所以,在真正开始编写逻辑之前,查看 screenshot 目录下的素材结构和展示结果的 html(即 screenshot.html) 的结构。发现这些图都在同一个文件夹,命名是切图序列 + 自定义文字。另外,还从 .gem 这个文件夹下,找到了 fastlane 的 snapshot 的源码,其中我们最感兴趣的是生成 screenshot.html 的 ruby 文件。

思考:如何在多次截图的文件夹里,对每一个主题分组呢?这就需要我们知道哪个图片属于哪个主题。换言之,

  1. 第一步,如何把 themeServerId 传给 XCUITest?
  2. 第二部,如何把 themeServerId 传给我们的自定义 action-resort_screenshot?

前面已经说过,从 fastlane 端传值给 XCUITest 是不行的;只剩下从 APP 里向 XCUITest 传值。

3.2 APP 里向 XCUITest 传值

经过仔细查找,发现 XCUITest 可以获取到 APP UIElement 里的 label,也就是 UIControl 的属性 accessibilityLabel。 所以解决方案是有钱 APP 在启动后,使用从 NSUserDefaults 获取的数据,写到 UIControl 的 accessibilityLabel,然后 XCUITest 使用

let xargs = app.staticTexts[“fastlane-snapshot-xargs”].label
let themeServerId: String = xargs.components(separatedBy: “=”)[1];

获取到 themeServerId,作为截图文件的名字的一部分。

3.3 XCUITest 向 resort_screenshot 传值

正如上述,因为 XCUITest 获取到 themeServerId,作为截图文件的名字的一部分。所以 resort_screenshot.rb 文件遍历图片目录下图片,经过一次循环之后,通过解析文件名,获取到了分组的类型和数量等数据,包括 themeServerIdresort_screenshot.rb 可以获取到 themeServerId,传值完成。

注意:XCUITest 向 resort_screenshot 传值,传哪些数据是密切和业务相关的,同时在 resort_screenshot.erb 文件对从 XCUITest 获取到的数据截取也是业务非常相关的。

后文提供的 action 的源码不是开箱即用的,需要在 XCUITest 的代码埋数据,在 resort_screenshot.rb 截取数据,再传递给 resort_screenshot.erb 渲染

至此,整个流程就走通了。themeServerId 数据的流向图是,

下面演示如何使用。

如何使用

在流程走通之后,需要移交给 QA,让他们可以在测试机上测试。在测试机上启动一个 web 服务,将测试机上 /screenshots/ 文件夹作为 Nginx 的 html 目录,使浏览器可访问此文件夹。

执行命令 fastlane test_ui_test themeServerId:002,005 devices:”iPhone 5″,测试完毕之后,自动打开浏览器,地址形如

http://youqian.com/screenshot/resort_screenshot.html

作为对照,你还可以打开 snapshot 默认的截图结果汇总界面。http://youqian.com/screenshot/screenshot.html

就可以看到测试结果了,这个测试结果地址可以给产品、或者视觉,供他们走查。

关于 fastlane test_ui_test 的参数 —- 完整的命令如,fastlane test_ui_test themeServerId:002,005 devices:”iPhone 5″。有两个参数,

  1. 一个是 themeServerId,接受 002, 005 这样的值,表示本次需要截图的主题有哪些,必需参数,这些参数对应前文表格数据,是业务相关,不具有通用性。
  2. 第二个是 devices,表示在哪些设备上截图。选填,默认是 iPhone 5,可以是多个,如”iPhone 5, iPhone 6 PLUS”,可选值是一组枚举值。但不推荐一次性测试多个设备,因为切换 iPhone、iPad 模拟器比较耗时。

让我们开始吧。我们的目的是测试 themeServerId 是 002,005 的两个皮肤的展示是否正确。执行 fastlane test_ui_test themeServerId:002,005 ,经过一段时间编译之后,会自动打开 iPhone 5 模拟器,自动截图,结束之后,会在控制台输出类似以下的信息,

当次任务执行的时间统计,这也是 fastlane 做工具聚合比较有用的一点——它的输出比较美观。并同时打开浏览器,会看到:

每个皮肤的测试作为一个组,以扇形显示。这样不会有横向滚动条。 点击某个扇形里的图片,会打开这张图片,进入大图浏览模式,可以使用左右箭头、或者键盘上的左右按钮来切换图片。

至此,大功告成。

其实为了实现这个功能,中间走了很多的弯路,下一节会集中列出来这些坑,或者某些注意事项,让刚刚接触到的人能够避开。

4. 注意事项

  1. 如果要使用工具链里的 match、cert、sign 等需要升级 openssl 到 2.0(使用 spaceship 需求)
  2. 为什么不通过 brew cask install fastlane 来安装呢?通过这种命令安装的 fastlane,所引用的 ruby 版本可能是系统默认的版本,如果你有多个 ruby 安装在系统上,会遇到很多无法预知的情况
  3. sudo gem install fastlane -NV 之后不一定成功,运行完毕之后,检查出错在哪儿,最常见的错误是 ruby 版本不对,如果需要多个 ruby 版本存在,推荐使用 rbenv。
  4. Fastfile 的写法很灵活,官网上很很多例子供我们参考,可以拿来吸取灵感。
  5. 细心的读者会发现,其实 SnapshotHelper.swift 是可以拿到 snapshot 注入的参数,那么我们的主测试代码里也可以拿到的,那为什么说 XCUITest 和 fastlane 不能通讯呢? 是的,其实是可以通讯的,但是 snapshot 注入的 launch_arguments 是 snapshot 自己通过创建了一个中间的 snapshot-launch_arguments.txt 文件来传递数据,我们自己写的测试类,是一个不属于 fastlane 体系的类,不应该依赖从黑盒中拿到的逻辑来实现自己的目的——所以我们认为 XCUITest 和 fastlane 之间无法直接通讯
  6. 记得追踪 fastlane 的版本更新。如 snapshot 的 launch_arguments 在某个版本 SnapshotHelper.swift 逻辑有问题,我排查了好久才发现是 SnapshotHelper.swift 的问题,更新到新版本就可以了。
  7. 现在维护 fastlane 的质量还有一些低级的错误,例如刚刚的一个升级提示。
          


看说明里,是各种修复语法错误,词法错误。没想到,最后的说明里还是有个拼写错误 ssigning。真是不应该啊。

结语

对于自动化测试主题这个任务,本身用到 fastlane 的技术不是很多,也没有很高级的技巧。本文期望作为一个科普类的文章,展示 fastlane 作为日常工作流程里,解决如何自动化问题的一种尝试。

在各个项目里,用 fastlane 可以解决什么问题,取决于自己的想象。

文末附上 resort_screenshot action 的源码,再次强调——并未开箱即用,需要和 XCUITest 的逻辑配合使用。

链接

  1. 从视觉到 App:网易有钱 iOS 项目主题自动化方案 – https://sq.163yun.com/blog/article/170380922858168320
  2. fastlane 在线文档 – https://docs.fastlane.tools/actions/
  3. 从 GPU 到 ImageNet,两位硅谷华人改变了 AI 发展史 | 总编专栏 – http://chuansong.me/n/1868638833335
  4. fastlane/Advanced.md at master – https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Advanced.md
  5. action 的源码 – https://github.com/hite/resort_screenshot.git

附录参考

—– EOF


本文来自网易实践者社区,经作者王亮授权发布。