【編者的話】這是持續內建系列的最後一篇,在本文中,作者列出了Martin Fowler撰寫的CI白皮書裡面的一些原則,并介紹了一些個人的實踐經驗。
本文講的是<b>持續內建(三):最佳實踐</b>這是持續內建系列的第三篇。在這篇文章裡,我們将介紹實作一個CI流程的一些最佳實踐。筆者也将會根據自己的行業經驗介紹一些真實世界裡的提醒和警告。
如果你還沒有讀過前面的文章的話,筆者強烈建議在繼續閱讀本文之前先翻閱一下!
下面,筆者将以自己個人的視角和大家一起來看看這些最佳實踐裡的每一項。
"這種做法主張對項目的源代碼使用一個修訂版控制系統。所有需要用來建構該項目的素材都應該放到倉庫裡。按照慣例,采取這樣的做法并且是在一個修訂版本控制社群裡,該系統應該是可以基于一個全新的簽出做建構,而無需任何額外的依賴。極限程式設計倡導者Martin Fowler還提到,在工具支援分支的情況下,對它的使用應該最小化。相反,我們推薦的是将變更內建進來而不是同時維護軟體的多個版本。主線(或者主幹)應該是軟體可工作版本代碼的存放位置。"
提醒
該原則不能單從字面上去了解,這并不意味着你隻需要一個單個的倉庫。這裡的關鍵是所有建構項目所需的素材都可以在一個倉庫裡找到。該項目應當可以基于一個全新的簽出建構,并且不需要額外的依賴或者手動步驟。
這裡'項目'的定義取決于你,如果代碼倉庫很小的話它可能意味着整個可傳遞的産品,或者可以是組織好的代碼裡任意邏輯子產品或者元件。
警告
該原則原本建議不要在版本控制系統裡使用分支。相反,它建議項目由始至終僅在一個單一分支下開發。
不過,筆者并不贊同這一點。在絕大多數組織裡,在多個分支下并行開發是很有必要的。企業往往需要支援産品之前釋出的版本,修複其中的錯誤,而其他的團隊成員則開始下一個版本的工作。這就需要在代碼庫裡維護多個分支。
"建構系統應當一條指令就能辦到。許多建構工具,比如make,已經存在很多年了。其他更多近期湧現的工具經常用在持續內建的環境裡。建構的自動化應該包含自動內建,這通常包括部署到一個類生産環境裡。在許多情況下,建構腳本不僅可以編譯二進制檔案,還可以生成文檔,網站頁面,統計資訊和發行版媒介(如Debian DEB,Red Hat RPM或Windows MSI檔案)。"
建構的自動化應當包括諸如編譯代碼,執行單元測試以及內建測試等步驟。 它們也許還包括許多其他工具 - 如前面文章裡描述的代碼品質檢查,語義檢查,衡量技術債等。絕大多數現代建構工具都支援這些額外的內建,而且應該可以用于建設持續內建環境。
在現實世界的項目裡,不同的團隊可能負責開發系統的不同部分,每個團隊都擁有自己的倉庫。 在這種情況下,幾乎不可能(沒有重大工作的話)而且也完全沒必要基于整個産品做自動化建構。 一般來說,為系統的每個單獨部分開發自動建構就足夠了。
定義CI流程的目的,即除了自動化建構流程外,是否還有其他的投入點?作為CI流程的一部分,你計劃測量哪些名額。很多時候,筆者見到的是CI設定被視為單獨隻是開發人員的工具。
延伸之前一點的話,CI不是靈活開發/DevOps,它們隻是針對整個組織成功實施CI流程所使用的工具之一。靈活開發/DevOps的範式可以超越軟體開發的技術層面,并擴充到組織的文化裡。
“一旦代碼被建構,所有測試都應該被執行以确認它的行為如開發人員們預期的那樣。”
代碼應當至少包含單元測試。像JUnit這樣的架構可用于輕松地模拟依賴。
特定元件與其他子產品的互動應當被模拟取代。 這可以確定一個子產品能夠獨立于其他子產品進行測試。
單元測試應該測試行為,而不是實作細節。有什麼差別呢?我們不妨通過一個例子來說明:測試行為的話:"我不關心你怎麼計算汽車的速度,保證答案是對的就行",如果是測試實作細節的話:"我不關心答案是什麼,隻要確定你采用的是這個公式:速度=距離/時間即可",測試行為是正确的方法,因為我們隻需要驗證結果就行了,而不是方案是如何實作的。
許多測試架構允許我們聲明模拟對象,即測試模拟對象是否被調用,是否在特定參數中被傳遞。這些資源應當被最小化,除非實作本身的測試是主要的關注點。
“通過定期送出,每位送出者都能借此減少沖突變更的次數。一周工作産出在簽入時與其他功能沖突的風險可能很難解決。在更早期的階段,系統某塊領域的小沖突會促使團隊成員就其所做的改變進行溝通。至少每天送出一次更改(每次建立一個功能)通常被認為是持續內建定義的一部分。此外,一般建議每晚進行一次建構。這些是下限;業内持續內建的典型頻率預計會高得多。”
代碼應至少包含單元測試。像JUnit這樣的架構可用于輕松地模拟依賴。
特定元件與其他子產品的互動應當被模拟取代。
(原文這一部分的Tips可能存在謬誤,譯者注)
已經完成的工作應當送出到主分支。主分支應當總是可工作版本的軟體代碼。
如果看到哪次建構失敗的話請不要送出分支。你應該先驗證下是什麼導緻的錯誤,然後嘗試盡快解決而不是送出自己的代碼。為什麼在建構失敗的時候不應該簽入你自己的代碼呢?首先,你自己的送出可能存在一些問題,它可能會破壞一些預期的行為。你不會知道這些問題是什麼,除非得知上一次簽入時建構的狀态。而且每一次簽入都有可能因為添加了現有的錯誤讓問題變得更糟。
“系統應當建構每一個合并到目前工作版本的送出,進而驗證它們內建地很好。常見的做法是利用自動持續內建,盡管這可以手動完成。對大多數情況而言,持續內建是采用自動持續內建的同義詞,一台持續內建伺服器或者守護程序會監控校訂版本控制系統的變更,随後自動運作建構流程。”
使用者應當分離主分支和其他分支的CI工作流。這些步驟包括從編譯到打包再到測試。主分支的建構一般應當包含更多的測試。主分支的建構也可能需要運作不同的腳本,因為應用可能需要針對不同的部署平台打包成不同的格式。在其他分支上運作的建構可能根本不需要打包這一步,或者通常局限于與開發人員相同的平台的打包。
“夜間建構”也應當在每晚計劃好的時間點執行。相比于其他分支而言,該建構應當包含更多的驗證過程。它需要花費更長時間去運作并且執行頻度更低。
主線分支裡不應該注釋測試。将測試注釋掉的話,我們得到的會是建構狀态的錯誤提示。
引入編碼标準的檢查是CI流程的一部分。代碼必須經過自動化工具以及團隊成員檢查,然後才能簽入到主線。
“建構需要快速完成,這樣一來如果存在內建問題便會立馬被識别出來。”
正如Martin Fowler所述,測試金字塔如下所示。使用者的目标應當是擁有更多比例的可以快速執行的測試。這意味着相比于其他類型的測試,使用者需要擁有更多的單元測試。

圖檔來源:MartinFowler.com
避免在單元測試中使用資料庫。如果可以的話,避免将其用于內建測試。一般來說內建測試需要采用一個替代資料源,通常指向的是一個記憶體資料庫。如果不可避免的需要使用真實資料庫進行測試的話,使用者需要保證在每次測試之前重新整理資料庫,確定資料處于已知狀态,并且測試不會基于不一緻的資料開始。
不要依賴大量的UI測試,UI測試是脆弱的,即他們是經常變動的,并且需要花費大量的精力去維護。筆者建議使用者使用像Selenium這樣的UI測試架構來規避UI測試過程中遇到的一些問題,例如UI元素在螢幕上位置的變動,UI事件的處理等。
“存在測試環境的話可能會導緻測試通過的系統部署到生産環境時發生故障,因為生産環境可能和測試環境有重大差異。然而,建設一個生産環境副本的成本是非常高昂的。相反,測試環境,或是一個單獨的預釋出環境('staging')應當被建設成實際生産環境的一個可擴充版本,在節省成本的同時維護技術棧的組成和它們之間的細微差别。在這些測試環境裡,人們常常使用服務虛拟化以通路那些超出團隊控制的依賴(例如,API,第三方應用,服務,大型機等),它們可能仍然在疊代發展,或是在一個虛拟測試實驗室裡的配置太過複雜。”
這是在現實世界的開發中付諸實踐時最難實作的一個原則。這需要建構自動化系統來建立并将軟體包部署到反映真實生産環境的一個灰階環境裡。 除非使用者的應用程式是自給自足的,沒有任何外部依賴,否則的話這一點很難實作,畢竟,生産環境的複雜度很高。筆者對複雜産品的建議是投入時間和精力借助虛拟化平台或容器平台(如Docker)來複制生産環境。持續傳遞的流水線可用于将建構部署到這些環境。
“為測試和其他相關人員提供建構結果,可以在重建不符合需求的功能時減少所需的返工量。此外,早期測試可以減少代碼缺陷在部署前的出鏡機會。更早地發現錯誤,在某些情況下,可以減少解決這些錯誤所需的工作量。每一位程式員都應該從更新倉庫項目代碼開始新的一天。這樣一來,它們都會保持最新。”
建議使用像Nexus這樣的資源倉庫來存放最新版本的軟體包。通常,存儲在這樣的資源倉庫中的包,它們也是通過版本号進行版本控制的。這使得所有參與者都可以輕松獲得目前或過去的任意建構包。
隻有主線分支中的建構包才能存放在資源倉庫裡。 如果不這麼做的話,每當有人建立一個正在工作的分支時會導緻現有軟體包被覆寫。
“我們應當能夠輕松找出建構是否有問題,如果是的話,誰做了相關的改動。”
所有的現代CI伺服器都有能力展示包含建構狀态的儀表盤。正如前面一篇文章所描述的那樣,這些也可以被配置成展示其他的一些名額。
所有CI伺服器也可以配置為,當建構完成時發送電子郵件通知。筆者建議在建構失敗時将電子郵件發送給整個團隊,以便可以盡快修複。
一次失敗的建構并不是奇恥大辱。每個人都會犯錯,開發人員也不能幸免。當建構失敗時,我們應當将其視為一個受歡迎的結果,因為該問題被及早地發現了。盡早失敗并且盡早修複問題是CI的關鍵目标。
CI不僅僅針對開發人員。通過安裝擴充,我們可以從CI系統導出各種名額,它們不僅可以用于提高軟體品質,還可以提高開發實踐的品質。
“大多數CI系統允許在建構完成後運作一些腳本。在大多數情況下,我們可以編寫腳本将應用程式部署到每個人都可以檢視的一台線上測試伺服器。這種思維模式的進一步演變便是持續部署,它需要将軟體直接部署到生産環境裡,這往往需要額外的自動化手段來防止缺陷或被還原。”
并非所有項目都需要自動部署,尤其是當企業伺服器運作在客戶站點。項目計劃決定了客戶站點更新到最新版本的時間,這通常是幾個月前就計劃好的。如果生産站點是同樣是由正在開發該軟體的公司自主托管的話,那麼對于持續部署系統的投入将更有收益。持續部署是持續內建流程到位并且運轉良好時後續的邏輯步驟。
并非所有的送出都能夠産出一個可傳遞的産品。靈活社群中最常見的誤解是認為每個版本都是可傳遞的産品。可傳遞的産品與能正常工作的軟體的定義完全不同!
筆者希望這些資訊可以讓使用者深入了解一些改進CI流程實施的最佳做法。CI在簡化軟體開發過程中發揮着重要作用。CI實踐的适當調整将提高軟體開發過程的整體效率和靈活性。結合這些最佳實踐是以更快的上市時間傳遞高品質軟體的訣竅!
<b>原文釋出時間為:</b>2017-10-15
<b>本文來自雲栖社群合作夥伴Dockerone.io,了解相關資訊可以關注Dockerone.io。</b>
<b></b>
<b>原文标題:</b><b>持續內建(三):最佳實踐</b>