Fastlane- 移动开发自动化之道
本人一直认为:在程序的世界里,一切重复性的,流程化的工作都可以交给自动化去完成。
在移动开发中也是如此:其实写代码只是我们开发过程中的一部分,除此之外我们还需要进行编译,打包,上传,部署,库管理,版本控制等等 Coding 之外的杂事,而正是这些乏味而重复的工作占用了我们宝贵的时间。
所以在“懒人”遍布的工程师世界中,总会有人想尽办法做出改变,于是这些“懒人”们乐此不疲的造出许多美妙的轮子,既方便了自己,又帮助了他人,让这个世界变得更加美好。
今天就给大家介绍其中一个轮子: Fastlane ,这个 Github 上的明星项目截止到目前共获得 1 万多个 Star,并且还有 1500 多个 Fork。
Fastlane 在我们团队中的推广和应用
怎么样,听起来是不是很牛?不过先别急,在进入正题之前,我想跟大家简单分享一下我们移动团队在开展持续测试和持续交付工作中的一些心得体会。
大家都知道,最近几年,随着智能手机的普及,移动端不仅要承载更多业务场景的实现,并且还要应对不断变化业务需求。这就要求我们移动团队能够迅速响应变化,快速的迭代。那么随之而来的问题就是如何保障在不牺牲质量的前提下,尽可能的提升速度,我认为这一切需要建立在高质量的持续测试和持续交付体系之上。
但是移动端本身兴起的时间就比较短,各方面的成熟度也有所欠缺,能够拿来用的工具更是少之又少,随着业务深度广度的增加,迭代速度的加快,诸如证书管理,打包,上传,发布这类重复而毫无技术含量的工作逐渐占用了大家的时间,团队内部对此诟病不已。
所以我们的架构团队从去年初就一直在寻找这样的一种工具,一种解决方案,旨在彻底解放工程师的“双手”。
刚开始我们尝试使用 Jenkins+Fir 搭建了一套持续测试的环境,流程如下图:
(点击放大图像)
说实话,效果还是可以的,至少在一定的时期内满足了我们的要求,但是Jenkins 本身只是一个通用的CI 流程管理系统,本身并不提供诸如ITC 提包和Meta 内容管理,签名,证书管理等等和移动端业务紧密结合的场景,而且配置的过程相当繁琐。
去年年底的时候,机缘巧合之下,我们在Github 上发现了Fastlane,看了Readme 后感觉有戏,于是决定尝试一下。其实刚开始的时候,我们也只是用Fastlane 来解决iOS 团队内证书同步和上传ITC 的问题,但是随着深入的研究,发现其实Fastlane 能做的更多,于是我们将其逐步应用到iOS 端的更多的场景,比如:私有Pod 的发布,代码的静态检查,UIAutomation 测试等等,接着又推广到Andriod 平台,完成诸如私有AAR 的发布,Monkey 测试等等一系列任务。现在可以说Fastlane 已经变成了我们工作中密不可分的一部分。
另外,Fastlane 本身也可以和Jenkins,Circle 等主流CI 系统做很好的集成,并且由于主要的CI 流程都由Fastlane 来管理和执行,所以从根本上降低了这些系统配置的复杂度。
Fastlane 简介
说了这么多,我们回到今天的主角身上,首先先简单介绍一下:Fastlane 是用 Ruby 语言编写的一套自动化工具集和框架,每一个工具实际都对应一个 Ruby 脚本,用来执行某一个特定的任务,而 Fastlane 核心框架则允许使用者通过类似配置文件的形式,将不同的工具有机而灵活的结合在一起,从而形成一个个完整的自动化流程。
到目前为止,Fastlane 的工具集大约包含 170 多个小工具,基本上涵盖了打包,签名,测试,部署,发布,库管理等等移动开发中涉及到的内容。
关于这些工具的描述和使用可以看这里: https://docs.fastlane.tools/actions/Actions/
如果这些工具仍然没有符合你需求的,没有关系,得益于 Fastlane 本身强大的 Action 和 Plugin 机制,如果你恰好懂一些 Ruby 开发的话,可以很轻易的编写出自己想要的工具。
其实真正官方出品的工具大约占一半左右,剩下的都是 Github 社区成员贡献的,本人有幸也贡献过其中一个。(这里建议移动开发工程师还是需要学习一门脚本语言的,比如 Ruby)
Fastlane 的安装非常简单,和 Cocoapods 一样,Fastlane 也可以通过 RubyGems 来安装,如果你的电脑上有 Ruby 环境的话,那么只需要执行如下命令,即可完成:
gem install fastlane
移动客户端持续测试和持续交付的常见场景及痛点
Fastlane 本身能做的事情很多,但是其中一个最为重要的作用就是能够无缝嵌入在持续测试和持续交付体系中。
下面,为了便于大家理解,我拿 iOS 为例,举几个我们在移动开发过程中常常会遇到的场景:
场景一
当一个迭代开发测试结束,服务器端上线之后,我们会使用 Testflight 进行线上跟测,一般会执行如下流程:
- 执行 Git Pull 命令,拉最新的代码到本地
- Pod Install 安装最新的依赖库
- 在 Xcode 中将 Build Version 增加
- 在 Xcode 点击 Archive 编译并打包
- 选择输出一个 iOS AppStore 模式的 ipa 文件
- 通过 Application Loader 将 IPA 上传到 ITC(TestFlight)
- 然后等待 ITC Process 完成后,登录上去选择刚才的 Build 进行 TestFlight 测试
- 由于修改了版本号,所以需要将代码 Commit 和 Push 一下
如果线上跟测发现有问题,那么需要修复完毕后重复上面的 8 个步骤。
其实做过这件事的同学应该都有体会,顺利的话差不多一次得 30 分钟吧,如果某一次 Build Version 忘记增加了,那么前面的工作就白做了。
在我们团队早期的时候,由于自动化体系尚未建立,我们有一个同事专门负责此事,在线上跟测的这两天,他有半天时间几乎干不了别的,基本上都在打包上传,说出来都是泪。
场景二
随着业务的发展,产品线的增加,我们需要将 APP 拆分为若干个基础组件和业务组件,以便跨 APP 使用,并且方便管理维护(这又是另外一个大的议题,就不在此赘述了)。每个组件都由一个私有 Pod 来管理,Pod 的发布和更新也成为了我们日常工作的一部分,对于这些 Pod,一般我们团队内部的原则是:谁制作,谁管理,谁发布,Pod 的负责人我们内部称之为库管,这件事也就分担到了每个库管身上,库管发布一个 Pod 的流程大约如下:
- 增加 Podspec 中的版本号
- 执行 pod lib lint 命令进行库验证
- Git Commit 代码
- Git Push 代码到远端
- 打一个 Git Tag
- 将 Tag Push 到远端
- 执行 pod repo push 命令发布库到私有仓库
如果只有两三个库的话,并且库的更新频率较低的时候,每次手动来处理还好。但是当库逐渐增多的时候这件事就变得相当麻烦,尤其是当顶层的库依赖底层库的时候,那么升级一个库,影响面将远远超过其本身,通过人工的方式处理的话,整个过程会变得相当痛苦。
说到这里,我相信但凡是操作过的同学,应该都对此深有感触。
所以我们来看看针对以上这两个场景,如何使用 Fastlane 来解决。
其实说起来也不难,首先在项目下执行:
fastlane init
然后跟随配置引导,填写 App 和 ITC 相关信息,然后 Fastlane 会在项目目录下创建一个 fastlane 目录,里面包含所有和此项目相关的配置,剩下要做的就是将以上的流程配置在 fastlane 目录下的 Fastfile 中,
场景一的 Fastfile(可以忽略 HipChat 部分):
desc 'Deploy a new version to the App Store' lane :do_deliver_app do |options| ENV["FASTLANE_PASSWORD"] = options[:itc_password] project = options[:project] scheme = options[:scheme] version = options[:version] build = options[:build] || Time.now.strftime('%Y%m%d%H%M') output_directory = options[:output_directory] output_name = options[:output_name] hipchat(message: "Start deilver app #{project} at version #{version}") hipchat(message: "Git pull") git_pull hipchat(message: "Pod install") cocoapods hipchat(message: "Update build number to #{build} and building ipa") update_build_number(version: build, plist: "#{project}/Info.plist") gym(scheme: options[:scheme], clean: true, output_directory: output_directory, output_name: output_name) hipchat(message: 'deliver to itunesconnect') deliver(force: false, skip_screenshots: true, skip_metadata: true) hipchat(message: "Upload #{project} to itunesconnect successfully!") git_add(path: '.') git_commit(path: '.', message: "update build number to #{build} and upload to itunesconnect") git_pull git_push(branch: "test") end
场景二的 Fastfile(可以忽略 HipChat 部分):
desc "Release new private pod version" lane :do_release_lib do |options| target_version = options[:version] project = options[:project] path = "#{project}.podspec" {1} hipchat(message: "Start release pod #{project} at version #{target_version}") {1} git_pull ensure_git_branch # 确认 master 分支 pod_install pod_lib_lint(verbose: true, allow_warnings: true, sources: SOURCES, use_bundle_exec: true, fail_fast: true) version_bump_podspec(path: path, version_number: target_version) # 更新 podspec git_commit_all(message: "Bump version to #{target_version}") # 提交版本号修改 add_git_tag(tag: target_version) # 设置 tag push_to_git_remote # 推送到 git 仓库 pod_push(path: path, repo: "GMSpecs", allow_warnings: true, sources: SOURCES) # 提交到 CocoaPods {1} hipchat(message: "Release pod #{project} Successfully!") end {1}
对于 Fastlane 的配置,官方提供的文档中会有更详细的描述:
https://docs.fastlane.tools/getting-started/ios/setup/
配置完这个脚本后,剩下的事就很轻松写意了。
针对场景一我们在项目目录下,用终端执行如下命令即可:
fastlane do_deliver_app project:Gengmei scheme:Gengmei-AppStore version:6.3.0 build:201609011530 ...
同理,针对场景二我们在项目目录下,用终端执行如下命令即可:
fastlane do_release_lib project:GMUtil version:0.1.4
任何复杂的流程,基本上一个命令全部搞定,是不是很方便。
另外,为了能够让 Fastlane 的各种命令更加傻瓜话,可视化,我们基于 Fastlane 内核,使用 Ruby on Rails 框架开发了一款适用于移动端持续测试和持续发布的系统 Jaguar。Jaguar 本身和 Gitlab,Sentry,Jira,HipChat,Maven 等等内部系统进行打通,从而形成一个完整的自动化体系。
(点击放大图像)
这样对于大部分的自动化流程,Jaguar 会通过各种定时任务或WebHook 自动触发;少部分需要人工操作的,工程师们也只需要点一个按钮就能完成了。
这里附上一个Jaguar 的截图:
(点击放大图像)
使用 Fastlane 的感受,踩坑的简单回顾
使用了 Fastlane 这么长的时间,我最深的感受就是:Fastlane 真正的将工程师从各种无聊而又必须要做的重复性劳动和流程化工作中解放出来,专注于业务或架构本身,使得整个开发效率,测试效率,运维效率大大提升。
在使用 Fastlane 的过程中,有一些小的注意事项,也和大家分享一下:
由于 Fastlane 本身更新频率比较高,大约 1-2 周一次,那么如果最近的几个版本都没有升级的话,建议仔细阅读一下这几次更新的 Release Notes,否则有可能会出现一些奇奇怪怪的 Bug,比如: 在 1.87 版本升级到 1.88 的时候,如果不在 Fastfile 中加入如下两行:
ENV['FASTLANE_EXPERIMENTAL_TRANSPORTER_AVOID_SHELL_SCRIPT'] = '1' ENV['SPACESHIP_LOGIN_ENCODING_IDENTITY'] = '1'
那么,上传 ITC 的时候,就会报错,而且从报错中并不能看到具体原因,最后经过各种折腾终于在 Github 上的 issue 中找到了原因:Spaceship 这个工具增加了一个小的特性而导致的 Bug。
另外,大家在使用 Fastlane 的过程中,如果遇到了问题,建议直接在 Github 上提 issue,Fastlane 的作者和社区工程师们会非常迅速的做出响应,而且会非常热心的帮你解决问题,当然大家提问题的时候要尽量描述清楚,该贴代码帖代码,该截图的截图。
发散思维,Fastlane 不只专属于移动端
虽然 Fastlane 本身是为移动而生的,但是如果我们发挥一下想象力的话,就会发现,其实也可以把后端,前端的持续集成和持续交付流程整理出来,然后也统一交给 Fastlane 来管理(当然这个过程中肯定需要自定义一些 Plugin 或 Action 的)
比如:我们团队目前正计划先把 PC 站的 UI 自动化测试流程集成进来:
- 执行 Git Pull 命令,拉最新的代码
- 使用 pip 安装 Requirements
- 混淆压缩前端 Javascript 和 CSS
- 重启 Django 服务
- 使用 Selenium 执行 UI 自动化测试
- 收集测试结果,发邮件给 QA 团队
对应的 Fastfile 如下(简写,还未真正使用):
desc "Do automation test for pc web" lane :automation_test_pc do |options| git_pull pip_install gulp_build restart_django selenium_test end
这样以来,一个团队内的客户端,前端,后端的持续集成和持续交付就能够统一部署,统一管理,统一维护,从而在基础设施上能够尽可能满足团队对自动化的需求,何乐而不为呢。
结语
本次分享只是带大家领略了一下 Fastlane 的风采,针对诸如:如何自定义 Action,Plugin,如何在 Andriod 平台上使用的细节,如何应用在自动化测试场景,以及一些 Fastlane 的高级用法等,我会在接下来的一段时间内做出相应的整理,形成文章,以供大家参考。
由于本人的水平有限,难免会有错误和疏漏,也欢迎各位同学指正,如果大家在 Fastlane 的使用上,有更好的案例,也欢迎交流和分享。
最后,附上一个我们团队正在使用到的 Fastfile 脚本和一些自定义 Actions:
https://github.com/GengmeiRD/Fastfiles
另外,Fastlane 也提供了一些国外团队的 Example:
https://github.com/fastlane/examples
感谢徐川对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们。
评论