持續整合(三):最佳實踐

貓飯先生發表於2017-10-09
【編者的話】這是持續整合系列的最後一篇,在本文中,作者列出了Martin Fowler撰寫的CI白皮書裡面的一些原則,並介紹了一些個人的實踐經驗。

引言

本文講的是持續整合(三):最佳實踐這是持續整合系列的第三篇。在這篇文章裡,我們將介紹實現一個CI流程的一些最佳實踐。筆者也將會根據自己的行業經驗介紹一些真實世界裡的提醒和警告。

快速回放:在本系列的第一篇裡,我們介紹了CI的基本概念以及它和敏捷開發及DevOps團隊文化的關聯。在第二篇裡,我們介紹了CI伺服器的概念以及它是如何將各種實現一個CI流程的行業標準實踐無縫整合到一起。

如果你還沒有讀過前面的文章的話,筆者強烈建議在繼續閱讀本文之前先翻閱一下!

Martin Fowler,在他的CI白皮書裡提到了一些應當成為任何CI設定一部分的關鍵實踐。這些建議多年來已然成為”這樣”一組持續整合的最佳實踐。同一主題的維基百科頁面則對外展示了Martin Fowler所闡述的那些原則的本質。

下面,筆者將以自己個人的視角和大家一起來看看這些最佳實踐裡的每一項。

最佳實踐

維護一個單一的原始碼倉庫

“這種做法主張對專案的原始碼使用一個修訂版控制系統。所有需要用來構建該專案的素材都應該放到倉庫裡。按照慣例,採取這樣的做法並且是在一個修訂版本控制社群裡,該系統應該是可以基於一個全新的簽出做構建,而無需任何額外的依賴。極限程式設計倡導者Martin Fowler還提到,在工具支援分支的情況下,對它的使用應該最小化。相反,我們推薦的是將變更整合進來而不是同時維護軟體的多個版本。主線(或者主幹)應該是軟體可工作版本程式碼的存放位置。”

提醒

  1. 該原則不能單從字面上去理解,這並不意味著你只需要一個單個的倉庫。這裡的關鍵是所有構建專案所需的素材都可以在一個倉庫裡找到。該專案應當可以基於一個全新的簽出構建,並且不需要額外的依賴或者手動步驟。
  2. 這裡`專案`的定義取決於你,如果程式碼倉庫很小的話它可能意味著整個可交付的產品,或者可以是組織好的程式碼裡任意邏輯模組或者元件。


警告

該原則原本建議不要在版本控制系統裡使用分支。相反,它建議專案由始至終僅在一個單一分支下開發。

不過,筆者並不贊同這一點。在絕大多陣列織裡,在多個分支下並行開發是很有必要的。企業往往需要支援產品之前釋出的版本,修復其中的錯誤,而其他的團隊成員則開始下一個版本的工作。這就需要在程式碼庫裡維護多個分支。

構建自動化

“構建系統應當一條命令就能辦到。許多構建工具,比如make,已經存在很多年了。其他更多近期湧現的工具經常用在持續整合的環境裡。構建的自動化應該包含自動整合,這通常包括部署到一個類生產環境裡。在許多情況下,構建指令碼不僅可以編譯二進位制檔案,還可以生成文件,網站頁面,統計資訊和發行版媒介(如Debian DEB,Red Hat RPM或Windows MSI檔案)。”

提醒

  • 構建的自動化應當包括諸如編譯程式碼,執行單元測試以及整合測試等步驟。 它們也許還包括許多其他工具 – 如前面文章裡描述的程式碼質量檢查,語義檢查,衡量技術債等。絕大多數現代構建工具都支援這些額外的整合,而且應該可以用於建設持續整合環境。
  • 在現實世界的專案裡,不同的團隊可能負責開發系統的不同部分,每個團隊都擁有自己的倉庫。 在這種情況下,幾乎不可能(沒有重大工作的話)而且也完全沒必要基於整個產品做自動化構建。 一般來說,為系統的每個單獨部分開發自動構建就足夠了。


警告

  • 定義CI流程的目的,即除了自動化構建流程外,是否還有其他的投入點?作為CI流程的一部分,你計劃測量哪些指標。很多時候,筆者見到的是CI設定被視為單獨只是開發人員的工具。
  • 延伸之前一點的話,CI不是敏捷開發/DevOps,它們只是針對整個組織成功實施CI流程所使用的工具之一。敏捷開發/DevOps的正規化可以超越軟體開發的技術層面,並擴充套件到組織的文化裡。


讓構建自檢

“一旦程式碼被構建,所有測試都應該被執行以確認它的行為如開發人員們預期的那樣。”

提醒

  • 程式碼應當至少包含單元測試。像JUnit這樣的框架可用於輕鬆地模擬依賴。
  • 特定元件與其他模組的互動應當被模擬取代。 這可以確保一個模組能夠獨立於其他模組進行測試。


警告

  • 單元測試應該測試行為,而不是實現細節。有什麼區別呢?我們不妨通過一個例子來說明:測試行為的話:”我不關心你怎麼計算汽車的速度,保證答案是對的就行”,如果是測試實現細節的話:”我不關心答案是什麼,只要確保你採用的是這個公式:速度=距離/時間即可”,測試行為是正確的方法,因為我們只需要驗證結果就行了,而不是方案是如何實現的。
  • 許多測試框架允許我們宣告模擬物件,即測試模擬物件是否被呼叫,是否在特定引數中被傳遞。這些資源應當被最小化,除非實現本身的測試是主要的關注點。


人人每天都提交到基線

“通過定期提交,每位提交者都能借此減少衝突變更的次數。一週工作產出在簽入時與其他功能衝突的風險可能很難解決。在更早期的階段,系統某塊領域的小衝突會促使團隊成員就其所做的改變進行溝通。至少每天提交一次更改(每次建立一個功能)通常被認為是持續整合定義的一部分。此外,一般建議每晚進行一次構建。這些是下限;業內持續整合的典型頻率預計會高得多。”

提醒

  • 程式碼應至少包含單元測試。像JUnit這樣的框架可用於輕鬆地模擬依賴。
  • 特定元件與其他模組的互動應當被模擬取代。


(原文這一部分的Tips可能存在謬誤,譯者注)

警告

  • 已經完成的工作應當提交到主分支。主分支應當總是可工作版本的軟體程式碼。
  • 如果看到哪次構建失敗的話請不要提交分支。你應該先驗證下是什麼導致的錯誤,然後嘗試儘快解決而不是提交自己的程式碼。為什麼在構建失敗的時候不應該簽入你自己的程式碼呢?首先,你自己的提交可能存在一些問題,它可能會破壞一些預期的行為。你不會知道這些問題是什麼,除非得知上一次簽入時構建的狀態。而且每一次簽入都有可能因為新增了現有的錯誤讓問題變得更糟。


應當構建每一次提交(到基線的)

“系統應當構建每一個合併到當前工作版本的提交,從而驗證它們整合地很好。常見的做法是利用自動持續整合,儘管這可以手動完成。對大多數情況而言,持續整合是採用自動持續整合的同義詞,一臺持續整合伺服器或者守護程式會監控校訂版本控制系統的變更,隨後自動執行構建流程。”

提醒

  • 使用者應當分離主分支和其他分支的CI工作流。這些步驟包括從編譯到打包再到測試。主分支的構建一般應當包含更多的測試。主分支的構建也可能需要執行不同的指令碼,因為應用可能需要針對不同的部署平臺打包成不同的格式。在其他分支上執行的構建可能根本不需要打包這一步,或者通常侷限於與開發人員相同的平臺的打包。
  • “夜間構建”也應當在每晚計劃好的時間點執行。相比於其他分支而言,該構建應當包含更多的驗證過程。它需要花費更長時間去執行並且執行頻度更低。


警告

  • 主線分支裡不應該註釋測試。將測試註釋掉的話,我們得到的會是構建狀態的錯誤提示。
  • 引入編碼標準的檢查是CI流程的一部分。程式碼必須經過自動化工具以及團隊成員檢查,然後才能簽入到主線。


保持構建速度

“構建需要快速完成,這樣一來如果存在整合問題便會立馬被識別出來。”

提醒

  • 正如Martin Fowler所述,測試金字塔如下所示。使用者的目標應當是擁有更多比例的可以快速執行的測試。這意味著相比於其他型別的測試,使用者需要擁有更多的單元測試。
    ui.jpg

    圖片來源:MartinFowler.com

  • 避免在單元測試中使用資料庫。如果可以的話,避免將其用於整合測試。一般來說整合測試需要採用一個替代資料來源,通常指向的是一個記憶體資料庫。如果不可避免的需要使用真實資料庫進行測試的話,使用者需要保證在每次測試之前重新整理資料庫,確保資料處於已知狀態,並且測試不會基於不一致的資料開始。


警告

不要依賴大量的UI測試,UI測試是脆弱的,即他們是經常變動的,並且需要花費大量的精力去維護。筆者建議使用者使用像Selenium這樣的UI測試框架來規避UI測試過程中遇到的一些問題,例如UI元素在螢幕上位置的變動,UI事件的處理等。

克隆一個生產環境做測試

“存在測試環境的話可能會導致測試通過的系統部署到生產環境時發生故障,因為生產環境可能和測試環境有重大差異。然而,建設一個生產環境副本的成本是非常高昂的。相反,測試環境,或是一個單獨的預釋出環境(`staging`)應當被建設成實際生產環境的一個可擴充套件版本,在節省成本的同時維護技術棧的組成和它們之間的細微差別。在這些測試環境裡,人們常常使用服務虛擬化以訪問那些超出團隊控制的依賴(例如,API,第三方應用,服務,大型機等),它們可能仍然在迭代發展,或是在一個虛擬測試實驗室裡的配置太過複雜。”

提醒

這是在現實世界的開發中付諸實踐時最難實現的一個原則。這需要構建自動化系統來建立並將軟體包部署到反映真實生產環境的一個灰度環境裡。 除非使用者的應用程式是自給自足的,沒有任何外部依賴,否則的話這一點很難實現,畢竟,生產環境的複雜度很高。筆者對複雜產品的建議是投入時間和精力藉助虛擬化平臺或容器平臺(如Docker)來複制生產環境。持續交付的流水線可用於將構建部署到這些環境。

獲取最新的可交付成果變得很容易

“為測試和其他相關人員提供構建結果,可以在重建不符合需求的功能時減少所需的返工量。此外,早期測試可以減少程式碼缺陷在部署前的出鏡機會。更早地發現錯誤,在某些情況下,可以減少解決這些錯誤所需的工作量。每一位程式設計師都應該從更新倉庫專案程式碼開始新的一天。這樣一來,它們都會保持最新。”

提醒

建議使用像Nexus這樣的資源倉庫來存放最新版本的軟體包。通常,儲存在這樣的資源倉庫中的包,它們也是通過版本號進行版本控制的。這使得所有參與者都可以輕鬆獲得當前或過去的任意構建包。

警告

只有主線分支中的構建包才能存放在資源倉庫裡。 如果不這麼做的話,每當有人新建一個正在工作的分支時會導致現有軟體包被覆蓋。

人人都可以看到最近一次構建的結果

“我們應當能夠輕鬆找出構建是否有問題,如果是的話,誰做了相關的改動。”

提醒

  • 所有的現代CI伺服器都有能力展示包含構建狀態的儀表盤。正如前面一篇文章所描述的那樣,這些也可以被配置成展示其他的一些指標。
  • 所有CI伺服器也可以配置為,當構建完成時傳送電子郵件通知。筆者建議在構建失敗時將電子郵件傳送給整個團隊,以便可以儘快修復。


警告

  • 一次失敗的構建並不是奇恥大辱。每個人都會犯錯,開發人員也不能倖免。當構建失敗時,我們應當將其視為一個受歡迎的結果,因為該問題被及早地發現了。儘早失敗並且儘早修復問題是CI的關鍵目標。
  • CI不僅僅針對開發人員。通過安裝擴充套件,我們可以從CI系統匯出各種指標,它們不僅可以用於提高軟體質量,還可以提高開發實踐的質量。


自動部署

“大多數CI系統允許在構建完成後執行一些指令碼。在大多數情況下,我們可以編寫指令碼將應用程式部署到每個人都可以檢視的一臺線上測試伺服器。這種思維模式的進一步演變便是持續部署,它需要將軟體直接部署到生產環境裡,這往往需要額外的自動化手段來防止缺陷或被還原。”

警告

  • 並非所有專案都需要自動部署,尤其是當企業伺服器執行在客戶站點。專案計劃決定了客戶站點升級到最新版本的時間,這通常是幾個月前就計劃好的。如果生產站點是同樣是由正在開發該軟體的公司自主託管的話,那麼對於持續部署系統的投入將更有收益。持續部署是持續整合流程到位並且運轉良好時後續的邏輯步驟。
  • 並非所有的提交都能夠產出一個可交付的產品。敏捷社群中最常見的誤解是認為每個版本都是可交付的產品。可交付的產品與能正常工作的軟體的定義完全不同!


小結

筆者希望這些資訊可以讓使用者深入瞭解一些改進CI流程實施的最佳做法。CI在簡化軟體開發過程中發揮著重要作用。CI實踐的適當調整將提高軟體開發過程的整體效率和靈活性。結合這些最佳實踐是以更快的上市時間交付高品質軟體的訣竅!

原文連結:Continuous Integration Part 3: Best Practices(翻譯:Colstuwjx)

原文釋出時間為:2017-10-15

本文作者:colstuwjx

本文來自雲棲社群合作伙伴Dockerone.io,瞭解相關資訊可以關注Dockerone.io。

原文標題:持續整合(三):最佳實踐


相關文章