本人一直認(rèn)為:在程序的世界里,一切重復(fù)性的,流程化的工作都可以交給自動(dòng)化去完成。
在移動(dòng)開(kāi)發(fā)中也是如此:其實(shí)寫代碼只是我們開(kāi)發(fā)過(guò)程中的一部分,除此之外我們還需要進(jìn)行編譯,打包,上傳,部署,庫(kù)管理,版本控制等等Coding之外的雜事,而正是這些乏味而重復(fù)的工作占用了我們寶貴的時(shí)間。
所以在“懶人”遍布的工程師世界中,總會(huì)有人想盡辦法做出改變,于是這些“懶人”們樂(lè)此不疲的造出許多美妙的輪子,既方便了自己,又幫助了他人,讓這個(gè)世界變得更加美好。
今天就給大家介紹其中一個(gè)輪子:Fastlane,這個(gè)Github上的明星項(xiàng)目截止到目前共獲得1萬(wàn)多個(gè)Star,并且還有1500多個(gè)Fork。
Fastlane在我們團(tuán)隊(duì)中的推廣和應(yīng)用
怎么樣,聽(tīng)起來(lái)是不是很牛?不過(guò)先別急,在進(jìn)入正題之前,我想跟大家簡(jiǎn)單分享一下我們移動(dòng)團(tuán)隊(duì)在開(kāi)展持續(xù)測(cè)試和持續(xù)交付工作中的一些心得體會(huì)。
大家都知道,最近幾年,隨著智能手機(jī)的普及,移動(dòng)端不僅要承載更多業(yè)務(wù)場(chǎng)景的實(shí)現(xiàn),并且還要應(yīng)對(duì)不斷變化業(yè)務(wù)需求。這就要求我們移動(dòng)團(tuán)隊(duì)能夠迅速響應(yīng)變化,快速的迭代。那么隨之而來(lái)的問(wèn)題就是如何保障在不犧牲質(zhì)量的前提下,盡可能的提升速度,我認(rèn)為這一切需要建立在高質(zhì)量的持續(xù)測(cè)試和持續(xù)交付體系之上。
但是移動(dòng)端本身興起的時(shí)間就比較短,各方面的成熟度也有所欠缺,能夠拿來(lái)用的工具更是少之又少,隨著業(yè)務(wù)深度廣度的增加,迭代速度的加快,諸如證書管理,打包,上傳,發(fā)布這類重復(fù)而毫無(wú)技術(shù)含量的工作逐漸占用了大家的時(shí)間,團(tuán)隊(duì)內(nèi)部對(duì)此詬病不已。
所以我們的架構(gòu)團(tuán)隊(duì)從去年初就一直在尋找這樣的一種工具,一種解決方案,旨在徹底解放工程師的“雙手”。
剛開(kāi)始我們嘗試使用Jenkins+Fir搭建了一套持續(xù)測(cè)試的環(huán)境,流程如下圖:

說(shuō)實(shí)話,效果還是可以的,至少在一定的時(shí)期內(nèi)滿足了我們的要求,但是Jenkins本身只是一個(gè)通用的CI流程管理系統(tǒng),本身并不提供諸如ITC提包和Meta內(nèi)容管理,簽名,證書管理等等和移動(dòng)端業(yè)務(wù)緊密結(jié)合的場(chǎng)景,而且配置的過(guò)程相當(dāng)繁瑣。
去年年底的時(shí)候,機(jī)緣巧合之下,我們?cè)贕ithub上發(fā)現(xiàn)了Fastlane,看了Readme后感覺(jué)有戲,于是決定嘗試一下。其實(shí)剛開(kāi)始的時(shí)候,我們也只是用Fastlane來(lái)解決iOS團(tuán)隊(duì)內(nèi)證書同步和上傳ITC的問(wèn)題,但是隨著深入的研究,發(fā)現(xiàn)其實(shí)Fastlane能做的更多,于是我們將其逐步應(yīng)用到iOS端的更多的場(chǎng)景,比如:私有Pod的發(fā)布,代碼的靜態(tài)檢查,UIAutomation測(cè)試等等,接著又推廣到Andriod平臺(tái),完成諸如私有AAR的發(fā)布,Monkey測(cè)試等等一系列任務(wù)?,F(xiàn)在可以說(shuō)Fastlane已經(jīng)變成了我們工作中密不可分的一部分。
另外,F(xiàn)astlane本身也可以和Jenkins,Circle等主流CI系統(tǒng)做很好的集成,并且由于主要的CI流程都由Fastlane來(lái)管理和執(zhí)行,所以從根本上降低了這些系統(tǒng)配置的復(fù)雜度。
Fastlane簡(jiǎn)介
說(shuō)了這么多,我們回到今天的主角身上,首先先簡(jiǎn)單介紹一下:Fastlane是用Ruby語(yǔ)言編寫的一套自動(dòng)化工具集和框架,每一個(gè)工具實(shí)際都對(duì)應(yīng)一個(gè)Ruby腳本,用來(lái)執(zhí)行某一個(gè)特定的任務(wù),而Fastlane核心框架則允許使用者通過(guò)類似配置文件的形式,將不同的工具有機(jī)而靈活的結(jié)合在一起,從而形成一個(gè)個(gè)完整的自動(dòng)化流程。
到目前為止,F(xiàn)astlane的工具集大約包含170多個(gè)小工具,基本上涵蓋了打包,簽名,測(cè)試,部署,發(fā)布,庫(kù)管理等等移動(dòng)開(kāi)發(fā)中涉及到的內(nèi)容。
關(guān)于這些工具的描述和使用可以看這里:https://docs.fastlane.tools/actions/Actions/
如果這些工具仍然沒(méi)有符合你需求的,沒(méi)有關(guān)系,得益于Fastlane本身強(qiáng)大的Action和Plugin機(jī)制,如果你恰好懂一些Ruby開(kāi)發(fā)的話,可以很輕易的編寫出自己想要的工具。
其實(shí)真正官方出品的工具大約占一半左右,剩下的都是Github社區(qū)成員貢獻(xiàn)的,本人有幸也貢獻(xiàn)過(guò)其中一個(gè)。(這里建議移動(dòng)開(kāi)發(fā)工程師還是需要學(xué)習(xí)一門腳本語(yǔ)言的,比如Ruby)
Fastlane的安裝非常簡(jiǎn)單,和Cocoapods一樣,F(xiàn)astlane也可以通過(guò)RubyGems來(lái)安裝,如果你的電腦上有Ruby環(huán)境的話,那么只需要執(zhí)行如下命令,即可完成:
gem install fastlane
移動(dòng)客戶端持續(xù)測(cè)試和持續(xù)交付的常見(jiàn)場(chǎng)景及痛點(diǎn)
Fastlane本身能做的事情很多,但是其中一個(gè)最為重要的作用就是能夠無(wú)縫嵌入在持續(xù)測(cè)試和持續(xù)交付體系中。
下面,為了便于大家理解,我拿iOS為例,舉幾個(gè)我們?cè)谝苿?dòng)開(kāi)發(fā)過(guò)程中常常會(huì)遇到的場(chǎng)景:
場(chǎng)景一
當(dāng)一個(gè)迭代開(kāi)發(fā)測(cè)試結(jié)束,服務(wù)器端上線之后,我們會(huì)使用Testflight進(jìn)行線上跟測(cè),一般會(huì)執(zhí)行如下流程:
- 執(zhí)行Git Pull命令,拉最新的代碼到本地
- Pod Install安裝最新的依賴庫(kù)
- 在Xcode中將Build Version增加
- 在Xcode點(diǎn)擊Archive編譯并打包
- 選擇輸出一個(gè)iOS AppStore模式的ipa文件
- 通過(guò)Application Loader將IPA上傳到ITC(TestFlight)
- 然后等待ITC Process完成后,登錄上去選擇剛才的Build進(jìn)行TestFlight測(cè)試
- 由于修改了版本號(hào),所以需要將代碼Commit和Push一下
如果線上跟測(cè)發(fā)現(xiàn)有問(wèn)題,那么需要修復(fù)完畢后重復(fù)上面的8個(gè)步驟。
其實(shí)做過(guò)這件事的同學(xué)應(yīng)該都有體會(huì),順利的話差不多一次得30分鐘吧,如果某一次Build Version忘記增加了,那么前面的工作就白做了。
在我們團(tuán)隊(duì)早期的時(shí)候,由于自動(dòng)化體系尚未建立,我們有一個(gè)同事專門負(fù)責(zé)此事,在線上跟測(cè)的這兩天,他有半天時(shí)間幾乎干不了別的,基本上都在打包上傳,說(shuō)出來(lái)都是淚。
場(chǎng)景二
隨著業(yè)務(wù)的發(fā)展,產(chǎn)品線的增加,我們需要將APP拆分為若干個(gè)基礎(chǔ)組件和業(yè)務(wù)組件,以便跨APP使用,并且方便管理維護(hù)(這又是另外一個(gè)大的議題,就不在此贅述了)。每個(gè)組件都由一個(gè)私有Pod來(lái)管理,Pod的發(fā)布和更新也成為了我們?nèi)粘9ぷ鞯囊徊糠郑瑢?duì)于這些Pod,一般我們團(tuán)隊(duì)內(nèi)部的原則是:誰(shuí)制作,誰(shuí)管理,誰(shuí)發(fā)布,Pod的負(fù)責(zé)人我們內(nèi)部稱之為庫(kù)管,這件事也就分擔(dān)到了每個(gè)庫(kù)管身上,庫(kù)管發(fā)布一個(gè)Pod的流程大約如下:
- 增加Podspec中的版本號(hào)
- 執(zhí)行pod lib lint命令進(jìn)行庫(kù)驗(yàn)證
- Git Commit代碼
- Git Push代碼到遠(yuǎn)端
- 打一個(gè)Git Tag
- 將Tag Push到遠(yuǎn)端
- 執(zhí)行pod repo push命令發(fā)布庫(kù)到私有倉(cāng)庫(kù)
如果只有兩三個(gè)庫(kù)的話,并且?guī)斓母骂l率較低的時(shí)候,每次手動(dòng)來(lái)處理還好。但是當(dāng)庫(kù)逐漸增多的時(shí)候這件事就變得相當(dāng)麻煩,尤其是當(dāng)頂層的庫(kù)依賴底層庫(kù)的時(shí)候,那么升級(jí)一個(gè)庫(kù),影響面將遠(yuǎn)遠(yuǎn)超過(guò)其本身,通過(guò)人工的方式處理的話,整個(gè)過(guò)程會(huì)變得相當(dāng)痛苦。
說(shuō)到這里,我相信但凡是操作過(guò)的同學(xué),應(yīng)該都對(duì)此深有感觸。
所以我們來(lái)看看針對(duì)以上這兩個(gè)場(chǎng)景,如何使用Fastlane來(lái)解決。
其實(shí)說(shuō)起來(lái)也不難,首先在項(xiàng)目下執(zhí)行:
fastlane init
然后跟隨配置引導(dǎo),填寫App和ITC相關(guān)信息,然后Fastlane會(huì)在項(xiàng)目目錄下創(chuàng)建一個(gè)fastlane目錄,里面包含所有和此項(xiàng)目相關(guān)的配置,剩下要做的就是將以上的流程配置在fastlane目錄下的Fastfile中,
場(chǎng)景一的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
場(chǎng)景二的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 # 確認(rèn) 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}") # 提交版本號(hào)修改
add_git_tag(tag: target_version) # 設(shè)置 tag
push_to_git_remote # 推送到 git 倉(cāng)庫(kù)
pod_push(path: path, repo: "GMSpecs", allow_warnings: true, sources: SOURCES) # 提交到 CocoaPods
hipchat(message: "Release pod #{project} Successfully!")
end
對(duì)于Fastlane的配置,官方提供的文檔中會(huì)有更詳細(xì)的描述:
https://docs.fastlane.tools/getting-started/ios/setup/
配置完這個(gè)腳本后,剩下的事就很輕松寫意了。
針對(duì)場(chǎng)景一我們?cè)陧?xiàng)目目錄下,用終端執(zhí)行如下命令即可:
fastlane do_deliver_app
project:Gengmei
scheme:Gengmei-AppStore
version:6.3.0
build:201609011530
...
同理,針對(duì)場(chǎng)景二我們?cè)陧?xiàng)目目錄下,用終端執(zhí)行如下命令即可:
fastlane do_release_lib project:GMUtil version:0.1.4
任何復(fù)雜的流程,基本上一個(gè)命令全部搞定,是不是很方便。
另外,為了能夠讓Fastlane的各種命令更加傻瓜話,可視化,我們基于Fastlane內(nèi)核,使用Ruby on Rails框架開(kāi)發(fā)了一款適用于移動(dòng)端持續(xù)測(cè)試和持續(xù)發(fā)布的系統(tǒng)Jaguar。Jaguar本身和Gitlab,Sentry,Jira,HipChat,Maven等等內(nèi)部系統(tǒng)進(jìn)行打通,從而形成一個(gè)完整的自動(dòng)化體系。

這樣對(duì)于大部分的自動(dòng)化流程,Jaguar會(huì)通過(guò)各種定時(shí)任務(wù)或WebHook自動(dòng)觸發(fā);少部分需要人工操作的,工程師們也只需要點(diǎn)一個(gè)按鈕就能完成了。
這里附上一個(gè)Jaguar的截圖:

使用Fastlane的感受,踩坑的簡(jiǎn)單回顧
使用了Fastlane這么長(zhǎng)的時(shí)間,我最深的感受就是:Fastlane真正的將工程師從各種無(wú)聊而又必須要做的重復(fù)性勞動(dòng)和流程化工作中解放出來(lái),專注于業(yè)務(wù)或架構(gòu)本身,使得整個(gè)開(kāi)發(fā)效率,測(cè)試效率,運(yùn)維效率大大提升。
在使用Fastlane的過(guò)程中,有一些小的注意事項(xiàng),也和大家分享一下:
由于Fastlane本身更新頻率比較高,大約1-2周一次,那么如果最近的幾個(gè)版本都沒(méi)有升級(jí)的話,建議仔細(xì)閱讀一下這幾次更新的Release Notes,否則有可能會(huì)出現(xiàn)一些奇奇怪怪的Bug,比如:
在1.87版本升級(jí)到1.88的時(shí)候,如果不在Fastfile中加入如下兩行:
ENV['FASTLANE_EXPERIMENTAL_TRANSPORTER_AVOID_SHELL_SCRIPT'] = '1'
ENV['SPACESHIP_LOGIN_ENCODING_IDENTITY'] = '1'
那么,上傳ITC的時(shí)候,就會(huì)報(bào)錯(cuò),而且從報(bào)錯(cuò)中并不能看到具體原因,最后經(jīng)過(guò)各種折騰終于在Github上的issue中找到了原因:Spaceship這個(gè)工具增加了一個(gè)小的特性而導(dǎo)致的Bug。
另外,大家在使用Fastlane的過(guò)程中,如果遇到了問(wèn)題,建議直接在Github上提issue,F(xiàn)astlane的作者和社區(qū)工程師們會(huì)非常迅速的做出響應(yīng),而且會(huì)非常熱心的幫你解決問(wèn)題,當(dāng)然大家提問(wèn)題的時(shí)候要盡量描述清楚,該貼代碼帖代碼,該截圖的截圖。
發(fā)散思維,F(xiàn)astlane不只專屬于移動(dòng)端
雖然Fastlane本身是為移動(dòng)而生的,但是如果我們發(fā)揮一下想象力的話,就會(huì)發(fā)現(xiàn),其實(shí)也可以把后端,前端的持續(xù)集成和持續(xù)交付流程整理出來(lái),然后也統(tǒng)一交給Fastlane來(lái)管理(當(dāng)然這個(gè)過(guò)程中肯定需要自定義一些Plugin或Action的)
比如:我們團(tuán)隊(duì)目前正計(jì)劃先把PC站的UI自動(dòng)化測(cè)試流程集成進(jìn)來(lái):
- 執(zhí)行Git Pull命令,拉最新的代碼
- 使用pip安裝Requirements
- 混淆壓縮前端Javascript和CSS
- 重啟Django服務(wù)
- 使用Selenium執(zhí)行UI自動(dòng)化測(cè)試
- 收集測(cè)試結(jié)果,發(fā)郵件給QA團(tuán)隊(duì)
對(duì)應(yīng)的Fastfile如下(簡(jiǎn)寫,還未真正使用):
desc "Do automation test for pc web"
lane :automation_test_pc do |options|
git_pull
pip_install
gulp_build
restart_django
selenium_test
end
這樣以來(lái),一個(gè)團(tuán)隊(duì)內(nèi)的客戶端,前端,后端的持續(xù)集成和持續(xù)交付就能夠統(tǒng)一部署,統(tǒng)一管理,統(tǒng)一維護(hù),從而在基礎(chǔ)設(shè)施上能夠盡可能滿足團(tuán)隊(duì)對(duì)自動(dòng)化的需求,何樂(lè)而不為呢。
結(jié)語(yǔ)
本次分享只是帶大家領(lǐng)略了一下Fastlane的風(fēng)采,針對(duì)諸如:如何自定義Action,Plugin,如何在Andriod平臺(tái)上使用的細(xì)節(jié),如何應(yīng)用在自動(dòng)化測(cè)試場(chǎng)景,以及一些Fastlane的高級(jí)用法等,我會(huì)在接下來(lái)的一段時(shí)間內(nèi)做出相應(yīng)的整理,形成文章,以供大家參考。
由于本人的水平有限,難免會(huì)有錯(cuò)誤和疏漏,也歡迎各位同學(xué)指正,如果大家在Fastlane的使用上,有更好的案例,也歡迎交流和分享。
最后,附上一個(gè)我們團(tuán)隊(duì)正在使用到的Fastfile腳本和一些自定義Actions:
https://github.com/thierryxing/Fastfiles
另外,F(xiàn)astlane也提供了一些國(guó)外團(tuán)隊(duì)的Example:
https://github.com/fastlane/examples