持續之根本
CI的核心目標是快速頻繁集成代碼,通過一些手段(編譯、代碼檢查、運行測試、覆蓋率等)來持續地提供及時有效的反饋,可持續從何而來?前提就是這些手段都是行之有效的。
將CI的那些手段對應到每一次集成中的各個步驟,這些步驟應該是值得信賴的,比如單元測試、集成測試、E2E測試,確保它們都能夠真實有效地反饋當前代碼庫的集成狀態。試想一下,集成中單元測試步驟雖然存在,但其實沒有運行任何有用的測試,亦或代碼中沒有添加任何測試,那么即便這一步通過了,發布的軟件也是不可靠的。持續之根本取決于每一步的可靠性。
以一個Java Web工程為例,一次集成通常含有以下步驟:
這里面的每一個步驟循序漸進,必須都是通過后才能持續往后走,而通過也必須是有意義的通過,而不只是亮了一個空殼綠燈。
實踐指導:
有些項目的E2E測試運行時間很長,E2E測試可能就選在夜間運行,而不是在打包前必須要運行的步驟。另外,有些項目只有單元測試。
CI何所為
所謂CI,本質上在一個可被觸及的中心服務器去集成那些提交到中心代碼庫的代碼,并將一些原本可以重復手工完成檢驗步驟自動化起來。
無CI論者NoCI
會說:“CI其實沒啥用,你們大可把代碼提交,我pull下來做集成。我在我的機器跑測試,然后進行部署,一旦掛了我通知你們...”
一句話包含了多少辛酸和無奈:
1. 每天那么多次提交,NoCI這哥們完全不能知曉其他人何時提交。
2. 運行了測試掛了,通知誰?都不知道是誰的提交導致掛了,提供不了即時的反饋。
3. 即便別人提交后通知他,那他每天要多次重復去做這項工作,恐怕沒有什么產出了,就等著被fire了。
4. 自己機器還要開發,還要運行很多其他程序,運行測試跑了1個小時,一天跑八次測試就該下班了。
聽起來,NoCI
這哥們也能做這些事情,只不過效果沒那么好,還附帶了被Fire的風險。實際上,CI也并不神奇,一句話簡單
概括CI何所為:
CI做的事情就是將重復的手工工作自動化管理起來,并提供即時有效的反饋。
實踐指導:
反過來想,CI能做的事情,你都能在本地手動去做,所以在搭建CI的時候,可以現在本地手動驗證你在CI所設置的任務是否正確。
踏上征途
從畢業到現在,經歷了多個不同項目,從!測試 && !CI
-->本地測試
-->CI && 測試
-->強制 CI && 測試
,對CI歷經了從0到1的過程,一開始非常享受CI所帶來的好處,如今便是身處酒巷,久而不覺酒香
的狀態:在一個項目啟動的時候,首先會在Iteration 0將CI作為必備的基礎設施搭建好,然后步入開發階段。
CI策略由簡入繁,簡單的比如一個Android的build,跑完單元測試、打包apk、發布apk就完事了,復雜的就好比一個微服務的CI,每一個微服務除了完善的單元測試
以及嚴格的集成測試
,還需要定義清楚各個微服務之間如下一些構建依賴關系:
1. 構建的順序,服務B會依賴服務A的構建參數或者只有A成功后才構建B。
2. 微服務部署的順序,定義哪些服務部署是允許失敗的,以及失敗后的救援措施。
3. 微服務在多個環境的部署時的版本控制。
實際中的CI實踐通常介于簡單和復雜之間,這里我以過去幾個項目經歷為常規CI的依據,主要包含了四個步驟:
1. 解析依賴(編譯)。
2. 運行測試。
3. 打包。
4. 部署/發布
最重要的屬2和4。因為測試是代碼庫質量的保證,而測試也是我們搭建CI的前提,沒有測試的CI猶如牛刀殺雞。而部署則是關系到軟件的交付,只有做到自動化部署發布,CI才算完成了它的最后使命(那些由某些客觀因素導致無法自動化部署的場景不在討論范圍之內)。
解析依賴(編譯)
CI通常開始源代碼的獲取,獲取了所有完整的代碼庫后,首先要做的事情便是解析項目所需的依賴并經行編譯。為什么會解析依賴呢?在實際開發過程中,項目往往依賴很多第三方類庫,開發人員在搭建本地開發環境的時候,首次需要消耗一定的時間去下載依賴,在后續開發過程中,只要所下載的依賴沒有被刪除,就不會重復下載。而CI也少不了這個過程,所以第一次構建的時候,也會需要去下載這些依賴,然后進行編譯。所以需要保證CI服務器能夠正常下載第三方依賴
。
當然,也有人說,把這些依賴直接放入代碼庫中不就完事了嗎?對,這就是我正要提的一個反模式,為什么稱之為反模式呢,以下簡單列出這種實踐的幾個弊端:
1. 代碼庫體積過大。第三方依賴往往體積較大(多則幾個GB),提交到代碼庫會導致代碼庫體積很大,而我們應該有責任去讓代碼庫盡量保持精簡。
2. 影響部署效率。第三方依賴體積過大,導致每次部署的時候需要傳輸的大量文件,較大程度影響了部署流程,降低效率。
3. 版本沖突。團隊中開發人員A升級了依賴的版本,而其他開發人員在本地也進行了升級,提交后會難免會引發沖突,造成不必要的麻煩。
4. 違背了DRY(Don't Repeat Yourself)。因為每個人都保存了一份完整的依賴,一旦有改動,需要通知并傳達給所有人大量的信息,而如果每個人只是持有依賴的定義(配置文件),相當于持有對象的引用,所有人根據引用來更新依賴,他們之間需要傳遞的信息就變得非常精簡。
實踐指導:
當開發、CI只能在內網中進行時,就無法從互聯網上下載依賴,此時需要在CI所在的內網中搭建一個私有倉庫,用來專門存放第三方依賴,項目所需要的依賴從該倉庫中下載。比如Java工程,可以搭建一個私有的Maven倉庫,Gradle在解析依賴的時候,將源指向該私有倉庫。
測試
運行測試的策略通常借鑒于測試金字塔,單元測試、集成測試、E2E測試。關于測試的最佳實踐則是將這三部分測試都涵蓋進去。首先CI會每次自動運行單元測試,然后自動運行集成測試,最后是E2E測試。完美的情況下,每一次提交三部分測試都會運行,只有所有測試通過后才進入下一個環節。而在實際中,有些項目E2E測試運行的時間較長,以至于對集成和部署造成了一定的影響,此時我們需要做一些優化措施,可以并發運行E2E測試(Jenkins中join pulgin):
實踐指導:
測試三步曲中,最難的是數據準備,要做到測試能夠并發運行,就需要在設計E2E測試用例的時候,保證Test Case的準備數據和數據回滾的獨立性,但考慮到每一個Test Case都完全獨立,成本較大(數據庫數據的初始化和回滾),通常是對同一個功能模塊中存在依賴關系的Test Case做一次數據準備和數據回滾,Test Case則按照一定的順序運行。或者根據并發Salve的粒度來劃分獨立邊界。
關于測試數據準備和數據回滾的邊界,舉個例子:
一個電子商務系統中有登錄、用戶管理、地址管理、商品管理模塊、購物車5個模塊,我們將登錄、用戶管理和地址管理模塊的測試放在的Slave A上,將商品管理和購物車模塊的測試放在Slave B上運行,邊界劃分的粒度就存在以下三種:
1. 以單個Test Case作為邊界,運行任何Test Case都會做一次初始化和回滾。(非常耗時)
2. 以功能模塊作為邊界,每運行一個模塊的測試做一次初始化和回滾。(較耗時)
3. 以Slave作為邊界,比如Salve A在運行E2E測試的只做一次初始化和回滾,且Slave A和Slave B的數據互不干擾。(推薦的方式)
打包
之前某位UX跟我們開發人員要炸包,一開始把我愣住了,后來得知她原來是想問工程構建的jar包。
沒錯,Java開發人員最常接觸包就是jar文件(jar包)或war文件(war包)。另外,Android工程則會輸出apk,IOS工程會輸出ipa,JavaScript工程則會輸出丑化后的css和js文件,等等。所以,CI打包步驟是繼測試后的一個打包步驟,它為部署做準備。以Java Web工程為例,CI在測試通過后,構建出一個war包,之后就將該war包存檔起來,供下一步使用。所構建的war需要兼顧以下幾點:
1. 包含程序運行所需要的第三方依賴。
2. 包含項目中涉及的資源文件(css、js、image、icon等)。
3. 壓縮css和js相關資源。
4. 不包含任何測試相關的代碼和類庫。
5. 不包含任何程序運行時不需要的文件。
部署/發布
通產情況下,部署/發布 往往被認為是從開發到生產環境的最后一步,在本文的范疇中,我也把它列為了最后一步(實際中,部署到生產環境后,運營監控才剛剛開始)。
依賴解析完成、自動化測試都通過、軟件包構建完畢,此時我們需要將軟件包部署到生產環境中供測試和用戶使用了。有人會說:“這個簡單,直接將軟件包部署到生產環境的服務器上,啟動服務不就完畢了!” 真有這么簡單?先來嘗試回答一下幾個問題:
1. 直接部署到生產環境,軟件不存在漏洞了嗎?
2. 軟件在交付前,回歸測試能夠直接在生產環境上進行嗎?
3. 在敏捷開發中,需要定期給客戶showcase,此時用什么環境來demo呢?
4. 軟件在正式上線前,用戶通常會安排一個期限的UAT(用戶驗收測試),這個環境又如何來?
5. 重新部署到生產環境,如果用戶正在使用著軟件怎么辦?
以上幾個問題引出了經典的Test
、Staging
、UAT
、Production
四個環境。分別用于測試、演示、用戶驗收、生產運營。另外,開發人員通常在本地機器會運行一個Dev
環境。下圖總結了以上四個環境的部署方式:
UAT
和 Production
環境被虛線框起來,因為這兩個環境在某些情況下是交付團隊是沒有權限去控制的,此時是由客戶的專門人員負責一鍵部署。
實踐指導:
通常部署后需要管理多個服務的啟動,而且這些服務存在依賴關系,它們的啟動順序也是有一定先后,可以使用surpervisord,來管理這些服務的啟動順序和重啟策略。
總結
對于一個敏捷團隊(高效協作和快速反饋),CI是項目交付中必不可少的基礎設施,無論你是一名開發人員還是測試人員或者是Team Lead(PM),都應該盡全力捍衛CI的地位。
注釋
- CI: Contiuous Integration,持續集成
- PM:Project Manager,項目經理
- UAT:User Acceptance Test,用戶驗收測試