×

Fastlane实战(一):移动开发自动化之道

96
老邢Thierry
2016.08.08 22:42* 字数 3680

本人一直认为:在程序的世界里,一切重复性的,流程化的工作都可以交给自动化去完成。

在移动开发中也是如此:其实写代码只是我们开发过程中的一部分,除此之外我们还需要进行编译,打包,上传,部署,库管理,版本控制等等Coding之外的杂事,而正是这些乏味而重复的工作占用了我们宝贵的时间。

所以在“懒人”遍布的工程师世界中,总会有人想尽办法做出改变,于是这些“懒人”们乐此不疲的造出许多美妙的轮子,既方便了自己,又帮助了他人,让这个世界变得更加美好。

今天就给大家介绍其中一个轮子:Fastlane,这个Github上的明星项目截止到目前共获得1万多个Star,并且还有1500多个Fork。

Fastlane在我们团队中的推广和应用

怎么样,听起来是不是很牛?不过先别急,在进入正题之前,我想跟大家简单分享一下我们移动团队在开展持续测试和持续交付工作中的一些心得体会。

大家都知道,最近几年,随着智能手机的普及,移动端不仅要承载更多业务场景的实现,并且还要应对不断变化业务需求。这就要求我们移动团队能够迅速响应变化,快速的迭代。那么随之而来的问题就是如何保障在不牺牲质量的前提下,尽可能的提升速度,我认为这一切需要建立在高质量的持续测试和持续交付体系之上。

但是移动端本身兴起的时间就比较短,各方面的成熟度也有所欠缺,能够拿来用的工具更是少之又少,随着业务深度广度的增加,迭代速度的加快,诸如证书管理,打包,上传,发布这类重复而毫无技术含量的工作逐渐占用了大家的时间,团队内部对此诟病不已。

所以我们的架构团队从去年初就一直在寻找这样的一种工具,一种解决方案,旨在彻底解放工程师的“双手”。

刚开始我们尝试使用Jenkins+Fir搭建了一套持续测试的环境,流程如下图:

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进行线上跟测,一般会执行如下流程:

  1. 执行Git Pull命令,拉最新的代码到本地
  2. Pod Install安装最新的依赖库
  3. 在Xcode中将Build Version增加
  4. 在Xcode点击Archive编译并打包
  5. 选择输出一个iOS AppStore模式的ipa文件
  6. 通过Application Loader将IPA上传到ITC(TestFlight)
  7. 然后等待ITC Process完成后,登录上去选择刚才的Build进行TestFlight测试
  8. 由于修改了版本号,所以需要将代码Commit和Push一下

如果线上跟测发现有问题,那么需要修复完毕后重复上面的8个步骤。
其实做过这件事的同学应该都有体会,顺利的话差不多一次得30分钟吧,如果某一次Build Version忘记增加了,那么前面的工作就白做了。
在我们团队早期的时候,由于自动化体系尚未建立,我们有一个同事专门负责此事,在线上跟测的这两天,他有半天时间几乎干不了别的,基本上都在打包上传,说出来都是泪。

场景二
随着业务的发展,产品线的增加,我们需要将APP拆分为若干个基础组件和业务组件,以便跨APP使用,并且方便管理维护(这又是另外一个大的议题,就不在此赘述了)。每个组件都由一个私有Pod来管理,Pod的发布和更新也成为了我们日常工作的一部分,对于这些Pod,一般我们团队内部的原则是:谁制作,谁管理,谁发布,Pod的负责人我们内部称之为库管,这件事也就分担到了每个库管身上,库管发布一个Pod的流程大约如下:

  1. 增加Podspec中的版本号
  2. 执行pod lib lint命令进行库验证
  3. Git Commit代码
  4. Git Push代码到远端
  5. 打一个Git Tag
  6. 将Tag Push到远端
  7. 执行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"
    
  hipchat(message: "Start release pod #{project} at version #{target_version}")
    
  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
    
  hipchat(message: "Release pod #{project} Successfully!")
end

对于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

这样对于大部分的自动化流程,Jaguar会通过各种定时任务或WebHook自动触发;少部分需要人工操作的,工程师们也只需要点一个按钮就能完成了。

这里附上一个Jaguar的截图:


jaguar-screenshot

使用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自动化测试流程集成进来:

  1. 执行Git Pull命令,拉最新的代码
  2. 使用pip安装Requirements
  3. 混淆压缩前端Javascript和CSS
  4. 重启Django服务
  5. 使用Selenium执行UI自动化测试
  6. 收集测试结果,发邮件给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/thierryxing/Fastfiles

另外,Fastlane也提供了一些国外团队的Example:
https://github.com/fastlane/examples

Fastlane实战
Web note ad 1