《持續交付:發佈可靠軟件的系統方法》第3章 持續集成

  • 2019 年 10 月 8 日
  • 筆記

第3章 持續集成

3.1 引言

  • 持續集成要求每當有人提交代碼時,就對整個應用進行構建,並對其執行全面的自動化測試集合。而且至關重要的是,假如構建或測試過程失敗,開發團隊就要停下手中的工作,立即修復它。持續集成的目標是讓正在開發的軟件一直處於可工作狀態
  • 持續集成是一種根本的顛覆。如果沒有持續集成,你開發的軟件將一直處於無法運行狀態,直至(通常是測試或集成階段)有人來驗證它能否工作。有了持續集成以後,軟件在每次修改之後都會被證明是可以工作的(假如有足夠全面的自動化測試集合的話)。即便它被破壞了,你也很快就能知道,並可以立即修復

3.2 實現持續集成

3.2.1 準備工作

  • 在開始做持續集成之前,你需要做三件事情

版本控制

  • 與項目相關的所有內容都必須提交到一個版本控制庫中,包括產品代碼、測試代碼、數據庫腳本、構建與部署腳本,以及所有用於創建、安裝、運行和測試該應用程序的東西

自動化構建

  • 你要能在命令行中啟動構建過程。必須滿足如下條件:人和計算機都能通過命令行自動執行應用的構建、測試以及部署過程

團隊共識

  • 持續集成不是一種工具,而是一種實踐。它需要開發團隊能夠給予一定的投入並遵守一些準則,需要每個人都能以小步增量的方式頻繁地將修改後的代碼提交到主幹上,並一致認同「修復破壞應用程序的任意修改是最高優先級的任務」

3.2.2 一個基本的持續集成系統

  • 為了做持續集成,你不一定就需要一個持續集成軟件,正如我們所說,它是實踐,並不是工具
  • 有幾個開源工具可供選擇,比如Hudson和受人尊敬的CruiseControl家族(CruiseControl、CruiseControl.NET和CruiseControl.rb)
  • 還有兩種商業化持續集成服務器為小團隊提供了免費版本,它們是ThoughtWorks Studios開發的Go以及JetBrains的TeamCity。其他流行的商業化持續集成服務器還包括Atlassian的Bamboo和Zutubi的Pulse。高端的發佈管理以及構建加速系統還有UrbanCode的AntHillPro、ElectricCloud的ElectricCommander,以及IBM的BuildForge,它們都可以用於簡單的持續集成
  • 一旦準備好要提交最新修改代碼時,請遵循如下步驟
    • (1) 查看一下是否有構建正在運行。如果有的話,你要等它運行完。如果它失敗了,你要與團隊中的其他人一起將其修復,然後再提交自己的代碼
    • (2) 一旦構建完成且測試全部通過,就從版本控制庫中將該版本的代碼更新到自己的開發環境上
    • (3) 在自己的開發機上執行構建腳本,運行測試,以確保在你機器上的所有代碼都工作正常
    • (4) 如果本地構建成功,就將你的代碼提交到版本控制庫中
    • (5) 然後等待包含你的這次提交的構建結果
    • (6) 如果這次構建失敗了,就停下手中做的事,在自己的開發機上立即修復這個問題,然後再轉到步驟(3)
    • (7) 如果這次構建成功,你可以小小地慶祝一下,並開始下一項任務

3.3 持續集成的前提條件

3.3.1 頻繁提交

  • 對於持續集成來說,我們最重要的工作就是頻繁提交代碼到版本控制庫。每天至少應該提交幾次代碼
  • 定期地將代碼提交到代碼主幹上會給我們帶來很多其他好處。比如,它使每次的修改都比較小,所以很少會使構建失敗。當你做了錯事或者走錯了路線時,可以輕鬆地回滾到某個已知的正確版本上
  • 前面我們特意提到過「要提交到主幹」。很多項目使用版本控制中的分支技術來進行大型團隊的管理。然而,當使用分支時,其實不可能真正地做到持續集成。因為如果你在分支上工作,那麼你的代碼就沒有和其他開發人員的代碼進行即時集成

3.3.2 創建全面的自動化測試套件

  • 自動化測試有很多種,其中有三類測試我們會在持續集成構建中使用,它們分別是單元測試、組件測試和驗收測試
  • 單元測試用於單獨測試應用程序中某些小單元的行為(比如一個方法、一個函數,或一小組方法或函數之間的交互)。它們通常不需要啟動整個應用程序就可以執行,而且也不需要連接數據庫(如果應用程序需要數據庫的話)、文件系統或網絡
  • 組件測試用於測試應用程序中幾個組件的行為。與單元測試一樣,它通常不必啟動整個應用程序,但有可能需要連接數據庫、訪問文件系統或其他外部系統或接口(這些可以使用「樁」,即stub技術)。組件測試的運行時間通常較長
  • 驗收測試的目的是驗證應用程序是否滿足業務需求所定義的驗收條件,包括應用程序提供的功能,以及其他特定需求,比如容量、有效性、安全性等。驗收測試最好採用將整個應用程序運行於類生產環境的運作方式

3.3.3 保持較短的構建和測試過程

  • 將驗收測試按功能塊進行分組通常是可取的。這樣,當僅修改了系統中的個別功能塊時,就可以單獨運行影響系統這部分功能的驗證測試。很多單元測試框架都提供這樣的分組功能
  • 項目由幾個模塊組成,而每個模塊的功能相對獨立。此時需要認真考慮如何在版本控制庫和持續集成服務器上合理地組織這些模塊

3.3.4 管理開發工作區

  • 開發環境的管理是特別重要的。當開發人員剛開始新任務時,應該總是從一個已知正確的狀態開始。他們應該能夠運行構建、執行自動化測試,以及在其可控的環境上部署其開發的應用程序,通常是在他們自己的開發機上。在本地開發環境上運行應用程序時,應確保所使用的自動化過程與持續集成環境中的一致
  • 達到這一目標的第一個先決條件就是細心的配置管理,其次是對第三方依賴的配置管理,最後就是確保自動化測試(包括冒煙測試)都能夠在開發機上運行

3.4 使用持續集成軟件

3.4.1 基本操作

  • 持續集成軟件包括兩個部分。第一部分是一個一直運行的進程,它每隔一定的時間就執行一個簡單的工作流程。第二部分就是提供展現這個流程運行結果的視圖,通知你構建和測試成功與否,讓你可以找到測試報告,拿到生成的安裝文件等

3.4.2 鈴聲和口哨

  • 你還可以在構建過程中對源代碼進行一些分析工作,包括分析測試覆蓋率、重複代碼、是否符合編碼標準、圈複雜度,以及其他一些健康指標,並將結果顯示在每個構建的總結報告中

3.5 必不可少的實踐

  • 持續集成是一種實踐,不是一個工具,它的有效性依賴於團隊紀律
  • 持續集成系統的目標是,確保軟件在任何時候都可以工作

3.5.1 構建失敗之後不要提交新代碼

  • 持續集成的第一忌就是明知構建已經失敗了,還向版本控制庫中提交新代碼。如果構建失敗,開發人員應該儘快找出失敗的原因,並修復它

3.5.2 提交前在本地運行所有的提交測試,或者讓持續集成服務器完成此事

  • 很多現代持續集成服務器還提供這樣一種功能,名字叫做預測試提交(pretested commit),也稱為個人構建(personal build)或試飛構建(preflight build)。使用這種特性,就不必自己進行提交,持續集成服務器將拿到你的本地變更,把它放在構建網格中運行提交測試
  • Pulse、TeamCity和 ElectricCommander這三種持續集成服務器都已經提供了這個功能

3.5.3 等提交測試通過後再繼續工作

  • 構建失敗是持續集成過程中一個平常且預料之中的事情。我們的目標是儘快發現錯誤,並消滅它們,而不是期待完美和零錯誤

3.5.4 回家之前,構建必須處於成功狀態

  • 我們並不建議你工作到很晚來修復失敗的構建,而是希望你有規律地儘早提交代碼,給自己足夠的時間處理可能出現的問題。或者,你可以第二天再提交。很多有經驗的開發人員在下班前一小時內不再提交代碼,而是把它作為第二天早上的第一件事情

3.5.5 時刻準備着回滾到前一個版本

  • 如果某次提交失敗了,無論採取什麼樣的行動,最重要的是儘快讓一切再次正常運轉起來。如果無法快速修復問題,無論什麼原因,我們都應該將它回滾到版本控制庫中前一個可工作的版本上,

3.5.6 在回滾之前要規定一個修復時間

  • 建立一個團隊規則:如果因某次提交而導致構建失敗,必須在十分鐘之內修復它。如果在十分鐘內還沒有找到解決方案的話,就將其回滾到版本控制系統中前一個好的版本。如果團隊能夠忍受,有時候也可以延長一段時間來修復它

3.5.7 不要將失敗的測試注釋掉

  • 有些開發人員常常為了能夠提交代碼,而將那些失敗的測試注釋掉。這種衝動是可以理解的,但卻是無法被容忍的一種錯誤行為

3.5.8 為自己導致的問題負責

3.5.9 測試驅動的開發

  • 只有非常高的單元測試覆蓋率才有可能保證快速反饋(這也是持續集成的核心價值)
  • 能夠達到完美單元測試覆蓋率的唯一方法就是使用測試驅動開發

3.6 推薦的實踐

3.6.1 極限編程開發實踐

  • 對於任何團隊,即使不採用其他實踐,只用持續集成也會給項目開發帶來很大改善,而若與其他實踐相結合的話,它的作用會更大。尤其是,除了測試驅動開發和我們前面講到的代碼集體所有權,你還應該考慮把重構作為高效軟件開發的基石

3.6.2 若違背架構原則,就讓構建失敗

  • 開發人員有時很容易忘記系統架構的一些原則。我們曾經使用過一種手段來解決這個問題,那就是寫一些提交時測試,用於證明這些原則沒有被破壞

3.6.3 若測試運行變慢,就讓構建失敗

  • 持續集成需要小步頻繁提交。如果提交測試要運行很長時間的話,這種長時間的等待會嚴重損害團隊的生產效率,他們將花費很長的時間等待構建和測試過程完成
  • 為了讓開發團隊注意到快速測試的重要性,可以這樣做:當某個測試運行超過一定時間後,就讓這次提交測試失敗。我們在上一個項目中使用的這一時間是兩秒

3.6.4 若有編譯警告或代碼風格問題,就讓測試失敗

  • 我們曾經用過一個比較成功的策略,即只要有編譯警告,就讓構建失敗,但我們的開發團隊常常把它叫做「納粹代碼」。這在某些場合可能有點兒苛刻,但作為強迫寫好代碼的一種實踐,還是很有效的
  • 代碼質量檢查的開源工具,如下所示:
    • Simian是一種可以識別大多數流行語言(包括純文本)中重複代碼的工具
    • JDepend是針對Java的免費版本,它有一個.NET的商業版本NDepend,它們擁有大量對設計質量進行評估的實用(和不太實用)的度量指標
    • CheckStyle可以對「爛代碼」做一些檢查,比如工具類中的公共構造函數、嵌套的代碼塊和比較長的代碼行。它也能找到缺陷和安全漏洞的一些常見根源。它還很容易被擴展。FxCop是它的.NET版本
    • FindBugs是一個Java軟件,它是CheckStyle的替代品,有一些相似的校驗功能

3.7 分佈式團隊

  • 從技術角度上看,最為簡單的方法(也是從流程角度上講最有效的方法)就是使用共享的版本控制系統和持續集成系統

3.7.1 對流程的影響

  • 為了使交付過程更加平穩,讓各團隊之間的人員做定期輪換也是非常必要的,這樣每個地方的成員都能與其他地方的團隊成員建立起一些私人交情。對於建立團隊成員間的信任來說,這是非常重要的,也是分佈式團隊中最先要面對的問題。通過視頻會議設備進行回顧會議、展示會、站立會議和其他常規會議也是可行的。還有一種不錯的技術,就是讓每個開發團隊使用屏幕錄像軟件錄製一下他們在當天所開發的功能

3.7.2 集中式持續集成

  • 一些功能更強大的持續集成服務器提供像「集中管理構建網格」和「高級授權機制」這種功能,用於把持續集成作為一個集中式服務,為大型分佈式團隊提供服務
  • 虛擬化技術可以與集中式持續集成服務很好地結合,只需要單擊一下按鈕就能利用已保存好的基線鏡像重建一個新的虛擬機

3.7.3 技術問題

  • 加大投入在各開發中心之間建立起足夠高帶寬的通信機制是非常必要的。考慮將集中式的版本控制庫遷到某種分佈式版本控制系統(比如Git或Mercurial)也是不錯的選擇
  • 一定要為每個地點的團隊都提供所有系統的系統級訪問權限,確保任何每個開發站的團隊不但可以訪問,而且可以修正那些與其換班相關的問題

3.7.4 替代方法

  • 在分佈於不同地理位置的團隊能夠有效合作的重要因素中,持續集成算是僅有的兩三種最重要因素之一。持續集成中的「持續」是很重要的

3.8 分佈式版本控制系統

  • DVCS(Distributed Version Control System, 分佈式版本控制系統)的興起是團隊合作方式的革命性改進。很多開源項目曾經使用電子郵件或論壇發帖的方式來提交補丁,而像Git和Mercurial這種工具讓開發人員之間、團隊之間以及分支與合併工作流時的打補丁變得極其簡單
  • 與集中式系統相比,DVCS引入了一個中間層:在本地工作區的修改必須先提交到本地庫,然後才能推送到其他倉庫,而更新本地工作區時,必須先從其他倉庫中將代碼更新到本地庫

3.9 小結

  • 如果本書所介紹的開發實踐里,你只想選擇其中一種的話,我們建議你選擇持續集成。我們一次又一次地看到該實踐提高了軟件開發團隊的生產率
  • 持續集成的實施還會迫使你遵循另外兩個重要的實踐:良好的配置管理和創建並維護一個自動化構建和測試流程
  • 一個好的持續集成系統是基石,在此之上你可以構建更多的基礎設施
    • 一個巨大的可視化指示器,用於顯示構建系統所收集到的信息,以提供高質量的反饋
    • 結果報告系統,以及針對自己測試團隊的安裝包
    • 為項目經理提供關於應用程序質量的數據的提供程序
    • 使用部署流水線,可以將其延展到生產環境,為測試人員和運維團隊提供一鍵式部署系統

書籍

  • 《持續集成》
  • 《解析極限編程》
  • xUnit Test Patterns: Refactoring Test Code
  • Growing Object-Oriented Software, Guided by Tests

工具

  • CruiseControl家族(CruiseControl、CruiseControl.NET和CruiseControl.rb)
  • CruiseControl (http://cruisecontrol.sourceforge.net/)
  • Hudson (Jenkins前身,https://hudson.dev.java.net/)
  • LuntBuild (http://luntbuild.javaforge.com/)
  • JetBrains的TeamCity (http://www.jetbrains.com/teamcity/)
  • AntHill Pro (http://www.anthillpro.com/)
  • Bamboo (http://www.atlassian.com/software/bamboo/)
  • QuickBuild (http://www.pmease.com/)
  • 持續集成:Pulse、TeamCity和 ElectricCommander
  • 代碼質量檢查:Simian、JDepend、NDepend、CheckStyle、FindBugs