投稿文章,作者:一縷殇流化隱半邊冰霜(@halfrost)
前言
眾所周知,現在App的競爭已經到了用戶體驗為王,質量為上的白熱化階段。用戶們都是很挑剔的。如果一個公司的推廣團隊好不容易砸了重金推廣了一個APP,好不容易有了一些用戶,由於一次線上的bug導致一批的用戶在使用中紛紛出現閃退bug,輕則,很可能前期推廣砸的錢都白費了,重則,口碑不好,未來也提升不起用戶量來了。靜下心來分析一下問題的原因,無外乎就是質量沒有過關就上線了。除去主觀的一些因素,很大部分的客觀因素我覺得可以被我們防范的。根據大神們提出的一套開發規范建議,CI + FDD,就可以幫助我們極大程度的解決客觀因素。本文接下來主要討論 Continuous Integration 持續集成(簡稱CI)
目錄
1.為什麼我們需要持續集成
2.持續化集成工具——Jenkins
3.iOS自動化打包命令——xcodebuild + xcrun 和 fastlane - gym 命令
4.打包完成自動化上傳 fir / 蒲公英 第三方平台
5.完整的持續集成流程
6.Jenkins + Docker
一、為什麼我們需要持續集成
談到為什麼需要的問題,我們就需要從什麼是來說起。那什麼是持續集成呢。
持續集成是一種軟件開發實踐:許多團隊頻繁地集成他們的工作,每位成員通常進行日常集成,進而每天會有多種集成。每個集成會由自動的構建(包括測試)來盡可能快地檢測錯誤。許多團隊發現這種方法可以顯著的減少集成問題並且可以使團隊開發更加快捷。
CI是一種開發實踐。實踐應該包含3個基本模塊,一個可以自動構建的過程,自動編譯代碼,可以自動分發,部署和測試。一個代碼倉庫,SVN或者Git。最後一個是一個持續集成的服務器。通過持續集成,可以讓我們通過自動化等手段高頻率地去獲取產品反饋並響應反饋的過程。
那麼持續集成能給我們帶來些什麼好處呢?這裡推薦一篇文章,文章中把Continuous integration (CI) and test-driven development (TDD)分成了12個步驟。然而帶來的好處成倍增加,有24點好處。
我來說說用了CI以後帶來的一些深有體會的優點。
1. 縮減開發周期,快速迭代版本
每個版本開始都會估算好開發周期,但是總會因為各種事情而延期。這其中包括了一些客觀因素。由於產品線增多,迭代速度越來越快,給測試帶來的壓力也越來越大。如果測試都在開發完全開發完成之後再來測試,那就會影響很長一段時間。這時候由於集成晚就會嚴重拖慢項目節奏。如果能盡早的持續集成,盡快進入上圖的12步驟的迭代環中,就可以盡早的暴露出問題,提早解決,盡量在規定時間內完成任務。
2. 自動化流水線操作帶來的高效
其實打包對於開發人員來說是一件很耗時,而且沒有很大技術含量的工作。如果開發人員一多,相互改的代碼沖突的幾率就越大,加上沒有產線管理機制,代碼倉庫的代碼質量很難保證。團隊裡面會花一些時間來解決沖突,解決完了沖突還需要自己手動打包。這個時候如果證書又不對,又要耽誤好長時間。這些時間其實可以用持續集成來節約起來的。一天兩天看著不多,但是按照年的單位來計算,可以節約很多時間!
3. 隨時可部署
有了持續集成以後,我們可以以天為單位來打包,這種高頻率的集成帶來的最大的優點就是可以隨時部署上線。這樣就不會導致快要上線,到處是漏洞,到處是bug,手忙腳亂弄完以後還不能部署,嚴重影響上線時間。
4. 極大程度避免低級錯誤
我們可以犯錯誤,但是犯低級錯誤就很不應該。這裡指的低級錯誤包括以下幾點:編譯錯誤,安裝問題,接口問題,性能問題。
以天為單位的持續集成,可以很快發現編譯問題,自動打包直接無法通過。打完包以後,測試掃碼無法安裝,這種問題也會立即被暴露出來。接口問題和性能問題就有自動化測試腳本來發現。這些低級問題由持續集成來暴露展現出來,提醒我們避免低級錯誤。
二、持續化集成工具——Jenkins
Jenkins 是一個開源項目,提供了一種易於使用的持續集成系統,使開發者從繁雜的集成中解脫出來,專注於更為重要的業務邏輯實現上。同時 Jenkins 能實施監控集成中存在的錯誤,提供詳細的日志文件和提醒功能,還能用圖表的形式形象地展示項目構建的趨勢和穩定性。
根據官方定義,Jenkins有以下的用途:
構建項目
跑測試用例檢測bug
靜態代碼檢測
部署
關於這4點,實際使用中還是比較方便的:
1.構建項目自動化打包可以省去開發人員好多時間,重要的是,Jenkins為我們維護了一套高質量可用的代碼,而且保證了一個純淨的環境。我們經常會出現由於本地配置出錯而導致打包失敗的情況。現在Jenkins就是一個公平的評判者,它無法正確的編譯出ipa,那就是有編譯錯誤或者配置問題。開發人員沒必要去爭論本地是可以運行的,拉取了誰誰誰的代碼以後就不能運行了。共同維護Jenkins的正常編譯,因為Jenkins的編譯環境比我們本地簡單的多,它是最純淨無污染的編譯環境。開發者就只用專注於編碼。這是給開發者帶來的便利。
2.這個可以用來自動化測試。在本地生成大批的測試用例。每天利用服務器不斷的跑這些用例。每天每個接口都跑一遍。看上去沒必要,但是實際上今天運行正常的系統,很可能由於今天的代碼改動,明天就出現問題了。有了Jenkins可以以天為單位的進行回歸測試,代碼只要有改動,Jenkins就把所有的回歸測試的用例全部都跑一遍。在項目工期緊張的情況下,很多情況測試都不是很重視回歸測試,畢竟很可能測一遍之後是徒勞的“無用功”。然而由於回歸測試不及時,就導致到最後發版的時候系統不可用了,這時候回頭查找原因是比較耗時的,查看提交記錄,看到上百條提交記錄,排查起來也是頭疼的事情。以天為單位的回歸測試能立即發現問題。測試人員每天可以專注按單元測試,一周手動一次回歸測試。這是給測試者帶來的便利。
3.這個是靜態代碼分析,可以檢測出很多代碼的問題,比如潛在的內存洩露的問題。由於Jenkins所在環境的純淨,還是可以發現一些我們本地復雜環境無法發現的問題,進一步的提高代碼質量。這是給質檢帶來的便利。
4.隨時部署。Jenkins在打包完成之後可以設定之後的操作,這個時候往往就是提交app到跑測試用例的系統,或者部署到內測平台生成二維碼。部署中不能安裝等一些低級問題隨之立即暴露。測試人員也只需要掃一下二維碼即可安裝,很方便。這也算是給測試帶來的便利。
以下的例子以2016-07-24 22:35的Weekly Release 2.15的版本為例。
我們來開始安裝Jenkins。從官網https://jenkins.io/ 上下載最新的pkg安裝包。
也可以下載jenkins.war, 然後運行Java -jar jenkins.war,進行安裝。
安裝完成之後,Safari可能會自動打開,如果沒有自動打開,打開浏覽器,輸入http://localhost:8080
這個時候可能會報一個錯誤。如果出現了這面的問題。出現這個問題的原因就是Java環境有問題,重新Java環境即可。
這個時候如果你重啟電腦會發現Jenkins給你新增了一個用戶,名字就叫Jenkins,不過這個時候你不知道密碼。你可能會去試密碼,肯定是是不對的,因為初始密碼很復雜。這個時候正確做法是打開http://localhost:8080 會出現下圖的重設初始密碼的界面。
按照提示,找到/Users/Shared/Jenkins/Home/ 這個目錄下,這個目錄雖然是共享目錄,但是有權限的,非Jenkins用戶/secrets/目錄是沒有讀寫權限的。
打開initialAdminPassword文件,復制出密碼,就可以填到網頁上去重置密碼了。如下圖
一路安裝過來,輸入用戶名,密碼,郵件這些,就算安裝完成了。
還是繼續登錄localhost:8080 ,選擇“系統管理”——“管理插件”,我們要先安裝一些輔助插件。
安裝GitLab插件
因為我們用的是GitLab來管理源代碼,Jenkins本身並沒有自帶GitLab插件,所以我們需要依次選擇 系統管理->管理插件,在“可選插件”中選中“GitLab Plugin”和“Gitlab Hook Plugin”這兩項,然後安裝。
安裝Xcode插件
同安裝GitLab插件的步驟一樣,我們依次選擇系統管理->管理插件,在“可選插件”中選中“Xcode integration”安裝。
安裝完了這個,我們就可以配置一個構建項目了。
點擊新建好的項目,進來配置一下General參數。
這裡可以設置包的保留天數還有天數。
接著設置源碼管理。
由於現在我用到的是GitLab,先配置SSH Key,在Jenkins的證書管理中添加SSH。在Jenkins管理頁面,選擇“Credentials”,然後選擇“Global credentials (unrestricted)”,點擊“Add Credentials”,如下圖所示,我們填寫自己的SSH信息,然後點擊“Save”,這樣就把SSH添加到Jenkins的全局域中去了。
如果正常的配置正確的話,是不會出現下圖中的那段紅色的警告。如果有下圖的提示,就說明Jenkins還沒有連通GitLab或者SVN,那就請再檢查SSH Key是否配置正確。
構建觸發器設置這裡是設置自動化測試的地方。這裡涉及的內容很多,暫時我也沒有深入研究,這裡暫時先不設置。有自動化測試需求的可以好好研究研究這裡的設置。
不過這裡有兩個配置還是需要是配置的
Poll SCM (poll source code management) 輪詢源碼管理
需要設置源碼的路徑才能起到輪詢的效果。一般設置為類似結果: 0/5 每5分鐘輪詢一次
Build periodically (定時build)
一般設置為類似: 00 20 * 每天 20點執行定時build 。當然兩者的設置都是一樣可以通用的。
格式是這樣的
分鐘(0-59) 小時(0-23) 日期(1-31) 月(1-12) 周幾(0-7,0和7都是周日)(更加詳細的設置看這裡)
構建環境設置
iOS打包需要簽名文件和證書,所以這部分我們勾選“Keychains and Code Signing Identities”和“Mobile Provisioning Profiles”。
這裡我們又需要用到Jenkins的插件,在系統管理頁面,選擇“Keychains and Provisioning Profiles Management”。
進入Keychains and Provisioning Profiles Management頁面,點擊“浏覽”按鈕,分別上傳自己的keychain和證書。上傳成功後,我們再為keychain指明簽名文件的名稱。點擊“Add Code Signing Identity”,最後添加成功後如下圖所示:
注意:我第一次導入證書和Provisioning Profiles文件,就遇到了一點小“坑”,我當時以為是需要證書,但是這裡需要的Keychain,並不是cer證書文件。這個Keychain其實在/Users/管理員用戶名/Library/keychains/login.keychain,當把這個Keychain設置好了之後,Jenkins會把這個Keychain拷貝到/Users/Shared/Jenkins/Library/keychains這裡,(Library是隱藏文件)。Provisioning Profiles文件也直接拷貝到/Users/Shared/Jenkins/Library/MobileDevice文件目錄下。
這樣Adhoc證書和簽名文件就在Jenkins中配置好了,接下來我們只需要在item設置中指定相關文件即可。
回到我們新建的item,找到構建環境,按下圖選好自己的相關證書和簽名文件。
接下來在進行構建的設置
我們這裡選擇執行一段打包腳本。腳本在下一章節詳細的講解。
構建後操作
這裡我們選擇Execute a set of scripts,這裡也是一個腳本,這個腳本用來上傳自動打包好的ipa文件。腳本在第四章節有詳細的講解。
至此,我們的Jenkins設置就全部完成了。點擊構建,就會開始構建項目了。
構建一次,各個顏色代表的意義如下:
天氣的晴雨表代表了項目的質量,這也是Jenkins的一個特色。
如果構建失敗了,可以去查看Console Output可以查看log日志。
三、iOS自動化打包命令——xcodebuild + xcrun 和 fastlane - gym 命令
在日常開發中,打包是最後上線不可缺少的環節,如果需要把工程打包成 ipa 文件,通常的做法就是在 Xcode 裡點擊 「Product -> Archive」,當整個工程 archive 後,然後在自動彈出的 「Organizer」 中進行選擇,根據需要導出 ad hoc,enterprise 類型的 ipa 包。雖然Xcode已經可以很完美的做到打包的事情,但是還是需要我們手動點擊5,6下。加上我們現在需要持續集成,用打包命令自動化執行就順其自然的需要了。
1. xcodebuild + xcrun命令
Xcode為我們開發者提供了一套構建打包的命令,就是xcodebuild
和xcrun命令。xcodebuild把我們指定的項目打包成.app文件,xcrun將指定的.app文件轉換為對應的.ipa文件。
具體的文檔如下:xcodebuild官方文檔、xcrun官方文檔
NAME xcodebuild – build Xcode projects and workspaces SYNOPSIS 1. xcodebuild [-project name.xcodeproj] [[-target targetname] … | -alltargets] [-configuration configurationname] [-sdk [sdkfullpath | sdkname]] [action …] [buildsetting=value …] [-userdefault=value …] 2. xcodebuild [-project name.xcodeproj] -scheme schemename [[-destination destinationspecifier] …] [-destination-timeout value] [-configuration configurationname] [-sdk [sdkfullpath | sdkname]] [action …] [buildsetting=value …] [-userdefault=value …] 3. xcodebuild -workspace name.xcworkspace -scheme schemename [[-destination destinationspecifier] …] [-destination-timeout value] [-configuration configurationname] [-sdk [sdkfullpath | sdkname]] [action …] [buildsetting=value …] [-userdefault=value …] 4. xcodebuild -version [-sdk [sdkfullpath | sdkname]] [infoitem] 5. xcodebuild -showsdks 6. xcodebuild -showBuildSettings [-project name.xcodeproj | [-workspace name.xcworkspace -scheme schemename]] 7. xcodebuild -list [-project name.xcodeproj | -workspace name.xcworkspace] 8. xcodebuild -exportArchive -archivePath xcarchivepath -exportPath destinationpath -exportOptionsPlist path 9. xcodebuild -exportLocalizations -project name.xcodeproj -localizationPath path [[-exportLanguage language] …] 10. xcodebuild -importLocalizations -project name.xcodeproj -localizationPath path
上面10個命令最主要的還是前3個。
接下來來說明一下參數:
-project -workspace:這兩個對應的就是項目的名字。如果有多個工程,這裡又沒有指定,則默認為第一個工程。
-target:打包對應的targets,如果沒有指定這默認第一個。
-configuration:如果沒有修改這個配置,默認就是Debug和Release這兩個版本,沒有指定默認為Release版本。
-buildsetting=value ...:使用此命令去修改工程的配置。
-scheme:指定打包的scheme。
上面這些是最最基本的命令。
上面10個命令的第一個和第二個裡面的參數,其中 -target
和 -configuration 參數可以使用 xcodebuild -list
獲得,-sdk 參數可由 xcodebuild -showsdks
獲得,[buildsetting=value ...] 用來覆蓋工程中已有的配置。可覆蓋的參數參考官方文檔:Xcode Build Setting Reference。
build Build the target in the build root (SYMROOT). This is the default action, and is used if no action is given. analyze Build and analyze a target or scheme from the build root (SYMROOT). This requires specifying a scheme. archive Archive a scheme from the build root (SYMROOT). This requires specifying a scheme. test Test a scheme from the build root (SYMROOT). This requires specifying a scheme and optionally a destination. installsrc Copy the source of the project to the source root (SRCROOT). install Build the target and install it into the target’s installation directory in the distribution root (DSTROOT). clean Remove build products and intermediate files from the build root (SYMROOT).
上面第3個命令就是專門用來打帶有Cocopods的項目,因為這個時候項目工程文件不再是xcodeproj了,而是變成了xcworkspace了。
再來說說xcrun命令。
Usage: PackageApplication [-s signature] application [-o output_directory] [-verbose] [-plugin plugin] || -man || -help Options: [-s signature]: certificate name to resign application before packaging [-o output_directory]: specify output filename [-plugin plugin]: specify an optional plugin -help: brief help message -man: full documentation -v[erbose]: provide details during operation
參數不多,使用方法也很簡單,xcrun -sdk iphoneos -v PackageApplication + 上述一些參數。
參數都了解之後,我們就來看看該如何用了。下面這個是使用了xcodebuild + xcrun命令寫的自動化打包腳本
# 工程名 APP_NAME="YourProjectName" # 證書 CODE_SIGN_DISTRIBUTION="iPhone Distribution: Shanghai ******* Co., Ltd." # info.plist路徑 project_infoplist_path="./${APP_NAME}/Info.plist" #取版本號 bundleShortVersion=$(/usr/libexec/PlistBuddy -c "print CFBundleShortVersionString" "${project_infoplist_path}") #取build值 bundleVersion=$(/usr/libexec/PlistBuddy -c "print CFBundleVersion" "${project_infoplist_path}") DATE="$(date +%Y%m%d)" IPANAME="${APP_NAME}_V${bundleShortVersion}_${DATE}.ipa" #要上傳的ipa文件路徑 IPA_PATH="$HOME/${IPANAME}" echo ${IPA_PATH} echo "${IPA_PATH}">> text.txt //下面2行是沒有Cocopods的用法 echo "=================clean=================" xcodebuild -target "${APP_NAME}" -configuration 'Release' clean echo "+++++++++++++++++build+++++++++++++++++" xcodebuild -target "${APP_NAME}" -sdk iphoneos -configuration 'Release' CODE_SIGN_IDENTITY="${CODE_SIGN_DISTRIBUTION}" SYMROOT='$(PWD)' //下面2行是集成有Cocopods的用法 echo "=================clean=================" xcodebuild -workspace "${APP_NAME}.xcworkspace" -scheme "${APP_NAME}" -configuration 'Release' clean echo "+++++++++++++++++build+++++++++++++++++" xcodebuild -workspace "${APP_NAME}.xcworkspace" -scheme "${APP_NAME}" -sdk iphoneos -configuration 'Release' CODE_SIGN_IDENTITY="${CODE_SIGN_DISTRIBUTION}" SYMROOT='$(PWD)' xcrun -sdk iphoneos PackageApplication "./Release-iphoneos/${APP_NAME}.app" -o ~/"${IPANAME}"
2. gym 命令
說到gym,就要先說一下fastlane。
fastlane是一套自動化打包的工具集,用 Ruby 寫的,用於 iOS 和 Android 的自動化打包和發布等工作。gym是其中的打包命令。
fastlane的官網看這裡, fastlane的github看這裡。
要想使用gym,先要安裝fastlane。
sudo gem install fastlane --verbose
fastlane包含了我們日常編碼之後要上線時候進行操作的所有命令。
deliver:上傳屏幕截圖、二進制程序數據和應用程序到AppStore
snapshot:自動截取你的程序在每個設備上的圖片
frameit:應用截屏外添加設備框架
pem:可以自動化地生成和更新應用推送通知描述文件
sigh:生成下載開發商店的配置文件
produce:利用命令行在iTunes Connect創建一個新的iOS app
cert:自動創建iOS證書
pilot:最好的在終端管理測試和建立的文件
boarding:很容易的方式邀請beta測試
gym:建立新的發布的版本,打包
match:使用git同步你成員間的開發者證書和文件配置
scan:在iOS和Mac app上執行測試用例
整個發布過程可以用fastlane描述成下面這樣
lane :appstore do increment_build_number cocoapods xctool snapshot sigh deliver frameit sh "./customScript.sh" slack end
Ps:這裡可能大家還會聽過一個命令叫 xctool
xctool是官方xcodebuild命令的一個增強實現,輸出的內容比xcodebuild直觀可讀得多。通過brew即可安裝。
brew install xctool
使用gym自動化打包,腳本如下:
#計時 SECONDS=0 #假設腳本放置在與項目相同的路徑下 project_path=$(pwd) #取當前時間字符串添加到文件結尾 now=$(date +"%Y_%m_%d_%H_%M_%S") #指定項目的scheme名稱 scheme="DemoScheme" #指定要打包的配置名 configuration="Adhoc" #指定打包所使用的輸出方式,目前支持app-store, package, ad-hoc, enterprise, development, 和developer-id,即xcodebuild的method參數 export_method='ad-hoc' #指定項目地址 workspace_path="$project_path/Demo.xcworkspace" #指定輸出路徑 output_path="/Users/your_username/Documents/" #指定輸出歸檔文件地址 archive_path="$output_path/Demo_${now}.xcarchive" #指定輸出ipa地址 ipa_path="$output_path/Demo_${now}.ipa" #指定輸出ipa名稱 ipa_name="Demo_${now}.ipa" #獲取執行命令時的commit message commit_msg="$1" #輸出設定的變量值 echo "===workspace path: ${workspace_path}===" echo "===archive path: ${archive_path}===" echo "===ipa path: ${ipa_path}===" echo "===export method: ${export_method}===" echo "===commit msg: $1===" #先清空前一次build gym --workspace ${workspace_path} --scheme ${scheme} --clean --configuration ${configuration} --archive_path ${archive_path} --export_method ${export_method} --output_directory ${output_path} --output_name ${ipa_name} #輸出總用時 echo "===Finished. Total time: ${SECONDS}s==="
四、打包完成自動化上傳 fir / 蒲公英 第三方平台
要上傳到 fir / 蒲公英 第三方平台,都需要注冊一個賬號,獲得token,之後才能進行腳本化操作。
1. 自動化上傳fir
安裝fir-clifir的命令行工具
需要先裝好ruby再執行
gem install fir-cli #上傳到fir fir publish ${ipa_path} -T fir_token -c "${commit_msg}"
2.自動化上傳蒲公英
#蒲公英上的User Key uKey="7381f97070*****c01fae439fb8b24e" #蒲公英上的API Key apiKey="0b27b5c145*****718508f2ad0409ef4" #要上傳的ipa文件路徑 IPA_PATH=$(cat text.txt) rm -rf text.txt #執行上傳至蒲公英的命令 echo "++++++++++++++upload+++++++++++++" curl -F "file=@${IPA_PATH}" -F "uKey=${uKey}" -F "_api_key=${apiKey}" http://www.pgyer.com/apiv1/app/upload
五、完整的持續集成流程
經過上面的持續化集成,現在我們就擁有了如下完整持續集成的流程
六、Jenkins + Docker
關於Jenkins的部署,其實是分以下兩種:
單節點(Master)部署
這種部署適用於大多數項目,其構建任務較輕,數量較少,單個節點就足以滿足日常開發所需。
多節點(Master-Slave)部署
通常規模較大,代碼提交頻繁(意味著構建頻繁),自動化測試壓力較大的項目都會采取這種部署結構。在這種部署結構下,Master通常只充當管理者的角色,負責任務的調度,slave節點的管理,任務狀態的收集等工作,而具體的構建任務則會分配給slave節點。一個Master節點理論上可以管理的slave節點數是沒有上限的,但通常隨著數量的增加,其性能以及穩定性就會有不同程度的下降,具體的影響則因Master硬件性能的高低而不同。
但是多節點部署又會有一些缺陷,當測試用例變得海量以後,會造成一些問題,於是有人設計出了下面這種部署結構,Jenkins + Docker
由於筆者現在的項目還處於單節點(Master)部署,關於多節點(Master-Slave)部署也沒有實踐經驗,改進版本的Docker更是沒有接觸過,但是如果有這種海量測試用例,高壓力的大量復雜的回歸測試的需求的,那推薦大家看這篇文章。
最後
以上就是我關於Jenkins持續集成的一次實踐經驗。分享給大家,如果裡面有什麼錯誤,歡迎大家多多指教。