
原文網(wǎng)址:https://martinfowler.com/articles/continuousIntegration.html

Martin Fowler

目錄
2.5 每次提交都應(yīng)該在集成機上構(gòu)建主線2.8 在生產(chǎn)環(huán)境的克隆中測試2.9 任何人都能輕松獲得最新的可執(zhí)行文件延伸閱讀
持續(xù)集成是一種軟件開發(fā)實踐,團(tuán)隊成員頻繁地將他們的工作成果集成在一起,通常每人每天至少提交一次,這樣每天就會有多次集成。每次集成都通過自動構(gòu)建(包括測試)進(jìn)行驗證,以便盡可能快地檢測集成錯誤。許多團(tuán)隊發(fā)現(xiàn)這種方法可以顯著減少集成問題,并允許團(tuán)隊更快地開發(fā)內(nèi)聚軟件。本文簡要介紹了持續(xù)集成技術(shù)及其應(yīng)用現(xiàn)狀。
我清楚地記得我第一次看到一個大型軟件項目。那時我在英國一家大型電子公司做暑期實習(xí)。我的經(jīng)理,QA小組的一員,帶我參觀了一個地方,我們進(jìn)入了一個令人沮喪的大倉庫,里面堆滿了立方體。我被告知這個項目已經(jīng)開發(fā)了幾年,目前正在集成,并且已經(jīng)集成了幾個月。他告訴我,沒有人真正知道完成集成需要多長時間。從中我學(xué)到了軟件項目的一個共同的故事:集成是一個漫長而不可預(yù)測的過程。其實不需要這樣。我在ThoughtWorks的同事和世界上許多其他人所做的大多數(shù)項目都不把集成當(dāng)成一個很嚴(yán)重的事兒。任何單個開發(fā)人員的工作都離共享的項目狀態(tài)只有幾個小時,并且可以在幾分鐘內(nèi)集成回去。任何集成錯誤都能被快速發(fā)現(xiàn)并得到迅速修正。這種鮮明的對比并不是昂貴復(fù)雜工具的結(jié)果。它的本質(zhì)在于一個簡單的實踐,也就是團(tuán)隊里的每個人都在頻繁的集成,通常是每天對于一個受控的源代碼存儲庫進(jìn)行集成。當(dāng)我向人們描述這一做法時,我通常會發(fā)現(xiàn)兩種反應(yīng):“這里不行”和“這樣做不會有多大區(qū)別”。人們在嘗試的過程中發(fā)現(xiàn),這比聽起來容易得多,而且對開發(fā)有著巨大的影響。因此,第三種常見的反應(yīng)是“是的,我們這樣做了——沒有它你怎么活?”術(shù)語“持續(xù)集成”起源于Kent Beck的極限編程開發(fā)過程,是最初的12個實踐之一。當(dāng)我開始在ThoughtWorks工作時,作為一名顧問,我鼓勵在我工作的項目中使用這種技術(shù)。Matthew Foemmel把我模糊的建議變成了實際行動,我們看到了這個項目從罕見而復(fù)雜的集成,變?yōu)槲颐枋龅牟皇悄敲磭?yán)重的事情。Matthew和我在這篇論文的原始版本中寫下了我們的經(jīng)驗,這篇論文一直是我網(wǎng)站上最受歡迎的論文之一。盡管持續(xù)集成是一種不需要特殊工具來部署的實踐,但我們發(fā)現(xiàn)使用持續(xù)集成服務(wù)器是很有用的。最著名的此類服務(wù)器是CruiseControl,這是一個開源工具,最初由ThoughtWorks的幾個人構(gòu)建,現(xiàn)在由一個很大的社區(qū)維護(hù)。從那時起,出現(xiàn)了其他一些CI服務(wù)器,有開源的,也有商用的——包括ThoughtWorks工作室的Cruise。
對于我來說,解釋什么是CI以及它是如何工作的最簡單的方法是展示一個快速的例子,說明它如何與一個小特性的開發(fā)一起工作。假設(shè)我必須對一個軟件添加一點功能,任務(wù)是什么并不重要,因為現(xiàn)在我假設(shè)它很小,可以在幾個小時內(nèi)完成。(稍后我們將探討更長的任務(wù)和其他問題。)首先,我將當(dāng)前集成源代碼的副本復(fù)制到本地開發(fā)機器上。我通過使用源代碼管理系統(tǒng),從主干簽出一個工作副本來實現(xiàn)這一點。上面那段話對于使用源代碼控制系統(tǒng)的人來說是有意義的,但是對于不使用源代碼控制系統(tǒng)的人來說是胡言亂語。所以讓我快速地為后者解釋一下。源代碼控制系統(tǒng)將項目的所有源代碼保存在存儲庫中。系統(tǒng)的當(dāng)前狀態(tài)通常稱為“主干”。開發(fā)人員可以隨時在自己的機器上生成主干的受控副本,這稱為“簽出”。開發(fā)人員機器上的副本稱為“工作副本”。(大多數(shù)情況下,你實際上是把你的工作副本更新到主干上——實際上和簽出也是一樣的。)現(xiàn)在我拿著我的工作副本,做任何我需要做的事情來完成我的任務(wù)。這將包括修改產(chǎn)品代碼,以及添加或更改自動化測試。持續(xù)集成假定軟件中有高度自動化的測試:我稱之為自測試代碼的工具。它們通常使用流行的XUnit測試框架的一個版本。當(dāng)我完成之后(通常在我工作的不同階段),我就在我的開發(fā)機器上執(zhí)行一個自動化的構(gòu)建。這將獲取工作副本中的源代碼,將其編譯并鏈接到可執(zhí)行文件中,然后運行自動測試。只有在所有的構(gòu)建和測試都沒有錯誤的情況下,整個構(gòu)建才被認(rèn)為是正確的。有了正確的構(gòu)建,我就可以考慮將更改提交到存儲庫中。當(dāng)然,問題是,在我有機會提交我的更改之前,其他人可能,而且通常已經(jīng)對主干進(jìn)行了更改。因此,首先我用他們的更改來更新我的工作副本,并重新構(gòu)建。如果他們的更改與我的更改沖突,在編譯或測試中將顯示為失敗。在這種情況下,我的責(zé)任是修復(fù)這個問題,并重復(fù)構(gòu)建,直到我可以建立一個與主干正確同步的工作副本。一旦我自己構(gòu)建了一個正確同步的工作副本,最終我就可以將我的更改提交到主干中,之后會更新存儲庫。然而,我的提交并沒有完成我的工作。此時,我們再次構(gòu)建,但這次是在基于主干代碼的集成服務(wù)器上。只有當(dāng)這個構(gòu)建成功時,我們才能說我的更改已經(jīng)完成。因為總有萬一,我可能會遺漏了我的機器上的東西,存儲庫沒有得到適當(dāng)?shù)母隆V挥挟?dāng)我提交的更改在集成服務(wù)器上成功構(gòu)建時,我的工作才能完成。這個集成構(gòu)建可以由我手動執(zhí)行,也可以由Cruise自動完成。如果兩個開發(fā)人員之間發(fā)生沖突,通常會在第二個提交的開發(fā)人員構(gòu)建其更新的工作副本時捕獲沖突。否則,集成構(gòu)建將失敗。無論哪種方式,錯誤都會被快速檢測到。此時,最重要的任務(wù)是修復(fù)它,并使構(gòu)建重新正常工作。在持續(xù)集成環(huán)境中,不應(yīng)該讓失敗的集成構(gòu)建保持在失敗狀態(tài)太久。一個好的團(tuán)隊一天應(yīng)該有很多正確的構(gòu)建。不好的構(gòu)建時有發(fā)生,但應(yīng)該迅速被修復(fù)。這樣做的結(jié)果是,有一個穩(wěn)定的軟件,工作正常,包含很少的錯誤。每個人都是從這個共享的穩(wěn)定的基礎(chǔ)上開發(fā)的,從來沒有離開這個基礎(chǔ)太遠(yuǎn),以至于需要很長時間才能集成回來。尋找錯誤會花更少的時間,因為錯誤很快就會顯現(xiàn)出來。
上面的故事是關(guān)于CI的概述,以及它在日常生活中是如何工作的。顯然,讓所有這些工作順利進(jìn)行并不僅僅是這些。我現(xiàn)在將重點介紹構(gòu)成有效CI的關(guān)鍵實踐。軟件項目涉及許多需要組合在一起才能構(gòu)建產(chǎn)品的文件。跟蹤所有這些文件,是一項重要的工作,特別是當(dāng)有多人參與時。因此,我們毫不意外的看到,多年來,軟件開發(fā)團(tuán)隊已經(jīng)構(gòu)建了管理所有這些文件的工具。這些工具稱為源代碼管理工具、配置管理、版本控制系統(tǒng)、存儲庫或各種其他名稱,是大多數(shù)開發(fā)項目不可分割的一部分。令人悲哀和驚訝的是,它們并不是所有項目的一部分。盡管很少見,但我確實遇到不使用這樣的系統(tǒng)的項目,項目使用一些混亂的本地和共享存儲器的組合。因此,作為一個簡單的基礎(chǔ),確保你要有一個像樣的源代碼管理系統(tǒng)。成本不是問題,因為有高質(zhì)量的開源工具。當(dāng)前選擇的開源存儲庫是Subversion。(較老的開源工具CVS仍然被廣泛使用,雖然比什么都沒有要好得多,但是Subversion是更時髦的選擇。)有趣的是,當(dāng)我與開發(fā)人員交談時,我了解到大多數(shù)商業(yè)源代碼管理工具比Subversion更受歡迎。我一直聽到人們說唯一值得花錢的工具就是Perforce。(譯者注:本文寫于2006年,時至今日,Git更為流行)一旦你得到一個源代碼管理系統(tǒng),確保它位于眾所周知的地方,每個人都去獲取源代碼。沒有人會問“foo-whiffle文件在哪里?”所有的東西都應(yīng)該在存儲庫里。盡管許多團(tuán)隊都會使用存儲庫,但我發(fā)現(xiàn)一個常見的錯誤是,他們沒有將所有內(nèi)容都放在存儲庫中。如果人們使用它,他們會把代碼放在那里,但你的構(gòu)建需要做的一切都應(yīng)該在那里,包括:測試腳本,屬性文件,數(shù)據(jù)庫架構(gòu),安裝腳本和第三方庫。我知道一些項目,將編譯器檢入到存儲庫(對于早期的大量的C++編譯器很重要)。基本的經(jīng)驗法則是,你應(yīng)該能夠用一臺空白的機器開始項目,做一個簽出,并且能夠完整的構(gòu)建系統(tǒng)。只有少量的東西應(yīng)該放在空白的機器上,通常是大的、安裝復(fù)雜的和穩(wěn)定的東西。操作系統(tǒng)、Java開發(fā)環(huán)境或基礎(chǔ)數(shù)據(jù)庫系統(tǒng)是典型的例子。你必須將構(gòu)建所需的所有內(nèi)容都放在源代碼管理系統(tǒng)中,但是你也可以將人們通常使用的其他內(nèi)容放在其中。IDE配置很適合放在那里,因為這樣人們就可以很容易地共享相同的IDE設(shè)置。版本控制系統(tǒng)的一個特點是,它們允許你能創(chuàng)建多個分支,以處理不同的開發(fā)流。這是一個有用的,但不必要的功能,但它經(jīng)常被過度使用,并使人們陷入麻煩。盡量少用分支。特別是在有一條主干的情況:目前正在開發(fā)的項目的唯一分支。幾乎每個人大部分時間都應(yīng)該在這條主干上工作。(合理的分支是修復(fù)先前生產(chǎn)版本的錯誤和臨時的實驗。)一般來說,你應(yīng)該在源代碼管理中存儲構(gòu)建所需的所有內(nèi)容,但不存儲實際構(gòu)建出的內(nèi)容。有些人確實將構(gòu)建的產(chǎn)品放在源代碼管理中,但我認(rèn)為這是一種壞味道——這意味著更深層次的問題,通常是無法可靠地重新創(chuàng)建構(gòu)建。將源代碼轉(zhuǎn)換為可以運行的系統(tǒng),通常是一個復(fù)雜的過程,它包括編譯、移動文件、把數(shù)據(jù)庫模式加載到數(shù)據(jù)庫等等。然而,與軟件開發(fā)中的大多數(shù)任務(wù)一樣,它是可以被自動化的。它也應(yīng)該是自動化的。讓人們輸入奇怪的命令或點擊對話框是浪費時間,也最容易產(chǎn)生錯誤。構(gòu)建自動化環(huán)境是系統(tǒng)中一個共同的特性。Unix世界使用make作為工具已經(jīng)幾十年了,Java社區(qū)發(fā)展了ANT(譯者注:后來JAVA的構(gòu)建工具發(fā)展為Maven和Gradle),并且. net社區(qū)已經(jīng)有了Nant,現(xiàn)在又有了MSBuild。確保你可以使用單個命令使用這些腳本構(gòu)建和運行系統(tǒng)。一個常見的錯誤是沒有在自動化構(gòu)建中包含所有內(nèi)容。構(gòu)建應(yīng)該包括從存儲庫中獲取數(shù)據(jù)庫模式,并在執(zhí)行環(huán)境中啟動它。我將詳細(xì)闡述我先前的經(jīng)驗法則:任何人都應(yīng)該能夠引入一臺空白機器,簽出存儲庫中的源代碼,發(fā)出一個命令,之后在自己的機器上就擁有了一個正在運行的系統(tǒng)。構(gòu)建腳本通常有不同的風(fēng)格,通常是特定于平臺或社區(qū)的,但他們大可不必如此。盡管我們的大多數(shù)Java項目都使用Ant,有一些在使用Ruby(Ruby Rake是一個非常好用的構(gòu)建腳本的工具)。我們通過使用Ant自動化在早期的微軟 COM項目中獲得了某些價值。一個大的構(gòu)建通常需要花費很多精力,如果你僅僅做了一個小小的更改,那么你不會想要執(zhí)行所有的步驟。所以,一個好的構(gòu)建工具會分析在流程中需要更改的內(nèi)容。通常的做法是檢查源文件和目標(biāo)文件的日期,只有在源文件的日期較晚時才進(jìn)行編譯。依賴關(guān)系會變得更加棘手:如果一個對象文件改變了那些依賴于它的對象文件,那么這些對象文件可能也需要重新構(gòu)建。編譯器能夠處理這類事情,也可能不處理。根據(jù)你的需要,你可以構(gòu)建不同類型的東西。你可以通過是否使用測試代碼或者使用不同的測試集來構(gòu)建系統(tǒng)。有些組件可以獨立構(gòu)建。構(gòu)建腳本應(yīng)該允許你為不同的情況構(gòu)建可選目標(biāo)。我們普遍使用IDE,而且在使用IDE時,大多數(shù)的公司內(nèi)部都有一些構(gòu)建管理的過程。然而,這些文件總是IDE專有的,而且它們非常脆弱。不過,他們這些公司需要通過IDE進(jìn)行工作。用戶通過IDE設(shè)置自己的項目文件并將其用于單獨的開發(fā)是完全沒有問題的。然而,有一個在服務(wù)器上可用并且可以從其他腳本運行的主干是非常重要的。所以在Java項目中,我們可以讓研發(fā)人員在IDE中構(gòu)建,但是主干需要使用Ant來保證它可以在開發(fā)服務(wù)器上運行。從傳統(tǒng)意義上來講,構(gòu)建意味著編譯,鏈接以及執(zhí)行程序所需的所有其他過程。一個項目可能會運行,但是,這并不意味著它在做正確的事情。現(xiàn)代靜態(tài)類型語言可以發(fā)現(xiàn)許多bug,但是更多的bug會成為“漏網(wǎng)之魚”。在構(gòu)建過程中包含自動化測試是更快、更有效地發(fā)現(xiàn)bug的比較好的方法。當(dāng)然,測試并不是完美的,但它能夠發(fā)現(xiàn)很多Bug,這就足夠有用了。特別是極限編程(XP)和測試驅(qū)動開發(fā)(TDD)的興起為自動化測試的普及做了大量工作,因此許多人已經(jīng)看到了這種技術(shù)的價值。經(jīng)常閱讀我作品的讀者會發(fā)現(xiàn)我是XP和TDD的忠實粉絲。然而,我想要強調(diào)的是,這兩種方式都不是構(gòu)建自動化測試的最佳途徑。這兩種方式都強調(diào)在使測試通過之前你要先編寫測試——在這種模式下,測試不僅能夠用于發(fā)現(xiàn)錯誤,而且還涉及探索系統(tǒng)的設(shè)計。這是一件好事情,但是,對持續(xù)集成而言,這并不是必需的。因為我們通常對自動化測試代碼的需求比較少。(盡管TDD是我進(jìn)行自動化測試的首選)對于自測試代碼而言,你需要一套自動化測試體系它可以檢查大部分代碼庫中的Bug。測試可以從一個簡單的指令中啟動并進(jìn)行自動檢測。運行測試套件的結(jié)果應(yīng)該可以指出是否有任何測試失敗。對于具備自測試的構(gòu)建,測試的失敗應(yīng)該會導(dǎo)致構(gòu)建失敗。在過去的幾年時間里,TDD的興起普及了XUnit開源工具家族,這些工具對于這類測試非常理想。Xunit 工具對我們 ThoughtWorks 來說非常有價值,我也總是建議人們使用這些工具。這些由Kent Beck首創(chuàng)的工具,能夠幫你非常容易的構(gòu)建一個自動化測試環(huán)境。XUnit工具當(dāng)然是讓代碼進(jìn)行自動化測試的起點。你也應(yīng)該尋找其他專注于更多端到端測試的工具。目前有很多這樣的工具,包括FIT、Selenium、Sahi、Watir、FITnesse,還有很多其他工具,我在這里不打算都列出來。當(dāng)然,你不能指望測試能發(fā)現(xiàn)一切錯誤。正如人們常說的那樣:測試并不能證明沒有缺陷。雖然你從自動化測試的構(gòu)建中得到的反饋并不一定是完美的,經(jīng)常運行的不完美的測試也比根本不寫的完美測試要好得多。集成主要是關(guān)于溝通的。集成允許開發(fā)人員將他們所做的更改告知其他開發(fā)人員。頻繁的交流能讓人們在變化發(fā)生時迅速了解情況。開發(fā)人員遵守主干的一個先決條件是,他們可以正確地構(gòu)建自己的代碼。當(dāng)然,這包括通過構(gòu)建測試。與任何提交周期一樣,開發(fā)人員首先更新其工作分支以匹配主干,解決與主干的任何沖突,然后在其本地上構(gòu)建。如果構(gòu)建通過,那么他們可以自由地提交到主干上。通過經(jīng)常這樣做,開發(fā)人員可以快速發(fā)現(xiàn)兩個開發(fā)人員之間是否存在沖突。快速修復(fù)問題的關(guān)鍵是快速找到它們。由于開發(fā)人員每隔幾小時就提交一次沖突,所以在沖突生后的幾小時內(nèi)就可以檢測到,此刻沒有發(fā)生太多代碼修改,所以容易解決。持續(xù)數(shù)周不被發(fā)現(xiàn)的沖突,可能很難解決。在更新工作分支時進(jìn)行構(gòu)建,這一事實意味著可以同時檢測到編譯沖突和文本沖突。由于構(gòu)建是自測試的,所以你還可以檢測代碼運行中的沖突,如果后一種Bug在代碼中存在了很長時間而沒有被發(fā)現(xiàn),那么它們是特別難以發(fā)現(xiàn)的錯誤。由于兩次提交之間只有幾個小時的更改,所以問題隱藏的地方也就只有那么多了。此外,由于沒有太大的變化,你可以使用差異調(diào)試來幫助你找到錯誤。我的一般經(jīng)驗法則是,每個開發(fā)人員每天都應(yīng)該提交到代碼庫。在實踐中,如果開發(fā)人員更頻繁地提交,通常是有用的。提交的頻率越高,尋找沖突錯誤的地方就越少,解決沖突的速度也就越快。頻繁的提交會鼓勵開發(fā)人員將他們的工作分解成幾個小時的小塊。這有助于跟蹤進(jìn)度,并提供一種進(jìn)度感。通常人們一開始覺得他們不能在幾個小時內(nèi)做一些有意義的事情,但是我們發(fā)現(xiàn)指導(dǎo)和練習(xí)可以幫助他們學(xué)習(xí)。2.5 每次提交都應(yīng)該在集成機上構(gòu)建主線通過使用每日構(gòu)建,團(tuán)隊可以得到頻繁的測試構(gòu)建。這應(yīng)該意味著主干保持在健康的狀態(tài)。然而,在實踐中,依然有出錯的情況。一個原因是紀(jì)律,人們沒有在提交之前進(jìn)行更新和構(gòu)建。另一個原因是開發(fā)人員使用的機器之間的環(huán)境差異。因此,你應(yīng)該確保常規(guī)構(gòu)建發(fā)生在集成機器上,并且只有在此集成構(gòu)建成功時,才應(yīng)該認(rèn)為提交已經(jīng)完成。由于開發(fā)人員對提交的代碼負(fù)責(zé),所以該開發(fā)人員需要監(jiān)視主干構(gòu)建,以便在它崩潰時進(jìn)行修復(fù)。這樣做的一個推論是,你如果在當(dāng)天晚些時候提交了一些改動,那么在主干構(gòu)建通過之前你不能回家。我見過兩種主要的方法來確保這一點:使用手動構(gòu)建或持續(xù)集成服務(wù)器。手工構(gòu)建方法是描述起來最簡單的方法。本質(zhì)上,它類似于開發(fā)人員在提交到存儲庫前進(jìn)行的本地構(gòu)建。開發(fā)人員走到集成計算機,簽出主線的頭部(現(xiàn)在存放他的最后提交),并開始集成構(gòu)建。他會密切關(guān)注構(gòu)建的進(jìn)展,如果構(gòu)建成功,他就完成了提交。(參見Jim Shore的描述。)持續(xù)集成服務(wù)器充當(dāng)存儲庫的監(jiān)視器。每次完成對代碼庫的提交時,服務(wù)器都會自動將源簽出到集成機器上,啟動構(gòu)建并將構(gòu)建的結(jié)果通知提交者。直到提交者收到通知——通常是一封電子郵件——它才算完成。在ThoughtWorks,我們是持續(xù)集成服務(wù)器的忠實粉絲——事實上,我們領(lǐng)導(dǎo)了CruiseControl和CruiseControl.NET的起初的開發(fā),他們是被廣泛使用的開源CI服務(wù)器。從那時起,我們還建立了商業(yè)版的Cruise CI服務(wù)器。我們幾乎在每個項目上都使用CI服務(wù)器,并且對結(jié)果非常滿意。并不是所有人都喜歡使用CI服務(wù)器。吉姆·肖爾(Jim Shore)對他為什么更喜歡手工方法給出了一個頗有爭議的描述。我同意他的觀點,CI不僅僅是安裝一些軟件。這里的所有實踐都需要有效地進(jìn)行持續(xù)集成。但是,同樣有許多優(yōu)秀的CI團(tuán)隊發(fā)現(xiàn)CI服務(wù)器是一個有用的工具。許多組織按照定時計劃進(jìn)行定期構(gòu)建,比如每天晚上。這與持續(xù)構(gòu)建不同,對于持續(xù)集成來說還不夠。持續(xù)集成的全部意義在于盡快發(fā)現(xiàn)問題。夜間構(gòu)建意味著bug在被發(fā)現(xiàn)之前一整天都沒有被發(fā)現(xiàn)。一旦它們在系統(tǒng)中存在了那么長時間,就需要花費很長時間才能找到并修復(fù)它們。做持續(xù)構(gòu)建非常關(guān)鍵的一點就是一旦主線構(gòu)建失敗就要立即修改它。使用CI的意義就在于,你總是能在已知穩(wěn)定基礎(chǔ)上進(jìn)行開發(fā)。主線構(gòu)建失敗是常有的事,且也并不是什么壞事,但這卻能夠表明人們在提交代碼之前對本地的更新和構(gòu)建不夠小心。然而,當(dāng)主線構(gòu)建確實中斷時,快速修復(fù)它就變得極為重要。我記得Kent Beck說過一句話:“沒有人擁有比修復(fù)失敗的構(gòu)建更高的優(yōu)先級任務(wù)”。但也不必團(tuán)隊中的每個人都必須停止他們手頭的工作來修改失敗的構(gòu)建,通常只需要幾個人就可以成功修復(fù)。我們要有意識的將修復(fù)構(gòu)建作為一個緊急的、高優(yōu)先級的任務(wù)進(jìn)行優(yōu)先排序。通常,修復(fù)構(gòu)建的最快方法是從主線還原到最新一次已知的、良好的構(gòu)建提交狀態(tài)。當(dāng)然,團(tuán)隊不應(yīng)該在構(gòu)建失敗的主線上進(jìn)行任何調(diào)試。除非失敗的原因很明顯,否則只需恢復(fù)主線然后在開發(fā)工作站上調(diào)試問題。為了避免破壞主線,你可以考慮使用pending head。當(dāng)團(tuán)隊引入CI時,這通常是最難解決的問題之一。在早期,團(tuán)隊很難養(yǎng)成處理主線構(gòu)建的常規(guī)習(xí)慣,特別是在處理現(xiàn)有代碼庫時。耐心和堅持不懈的努力看起來確實經(jīng)常起作用,,所以不要氣餒。持續(xù)集成的關(guān)鍵是提供快速的反饋。沒有什么比一個需要很長時間的構(gòu)建更能危害CI的活動了。在這里我必須承認(rèn)有一些古怪的老員工把長時間的構(gòu)建當(dāng)成娛樂。我的大多數(shù)同事認(rèn)為一個需要一個小時的構(gòu)建是完全不合理的。我記得一些團(tuán)隊夢想著他們能以如此之快的速度完成任務(wù)——但是有時候我們?nèi)匀粫龅竭@樣的情況:很難以這樣的速度完成任務(wù)。然而,對于大多數(shù)項目來說,十分鐘構(gòu)建的XP指導(dǎo)方針是完全合理的。現(xiàn)在我們的大多數(shù)項目都實現(xiàn)了這一點。這值得我們集中精力來實現(xiàn)它,因為每減少一分鐘的構(gòu)建時間,那么對于每個開發(fā)人員的每次提交都會節(jié)省一分鐘的時間。由于CI需要頻繁提交,這就會增加很多的時間。如果你一開始就需要一個小時的構(gòu)建時間,那么獲得更快的構(gòu)建可能會讓人畏懼。甚至在一個新的項目上工作,并思考如何讓事情保持快速也會讓人畏懼。至少對于企業(yè)應(yīng)用程序,我們發(fā)現(xiàn)通常的瓶頸是測試——特別是涉及外部服務(wù)(如數(shù)據(jù)庫)的測試。最關(guān)鍵的一步是開始建立部署流水線。部署流水線(也稱為構(gòu)建流水線或分階段構(gòu)建)背后的思想是,實際上有多個按順序完成的構(gòu)建。對主線的提交觸發(fā)了第一個構(gòu)建——我稱之為提交構(gòu)建。提交構(gòu)建是當(dāng)有人需要提交到主線時所需的構(gòu)建。提交構(gòu)建是必須快速完成的構(gòu)建,因此它將采用許多快捷方式,這將降低檢測錯誤的能力。關(guān)鍵在于平衡發(fā)現(xiàn)bug的能力和速度的需求,這樣一個好的提交構(gòu)建就足夠穩(wěn)定,可以供其他人工作使用一旦有良好的提交構(gòu)建,其他人就可以滿懷信心地處理代碼。不過,你可以開始做更多、更漫長的測試。其他機器可以在構(gòu)建上運行需要更長時間的測試程序。這是一個簡單的兩階段部署流水線例子。第一階段將進(jìn)行編譯并運行更本地化的單元測試,數(shù)據(jù)庫通過“打樁”的方式完全被隔離掉。這樣測試可以運行得非常快,保持在10分鐘的范圍內(nèi)。但是,任何涉及大規(guī)模交互的bug,特別是涉及真實數(shù)據(jù)庫的bug,都很難被發(fā)現(xiàn)。第二階段構(gòu)建運行一組不同的測試,這些測試確實會測試真實的數(shù)據(jù)庫,并涉及更多的端到端行為。這一次運行可能需要幾個小時的時間。在這個場景中,人們使用第一個階段作為提交構(gòu)建,并使用它作為主CI周期。第二階段構(gòu)建在可能的情況下運行,從最近的良好提交構(gòu)建中獲取可執(zhí)行文件以進(jìn)行進(jìn)一步的測試。如果這個二次構(gòu)建失敗,那么它可能沒有 “停止一切”的質(zhì)量目標(biāo),但是團(tuán)隊的目標(biāo)是在保持提交構(gòu)建運行的同時,盡快修復(fù)此類錯誤。在這個例子中,后面的構(gòu)建通常是純測試,因為現(xiàn)在通常是測試導(dǎo)致了緩慢。如果第二級構(gòu)建檢測到一個bug,這表明提交構(gòu)建可能使用另一套測試。盡可能地確保任何后期階段的失敗,都會觸發(fā)在提交構(gòu)建中引入新的測試,這樣在提交構(gòu)建中就能捕獲bug,從而在提交構(gòu)建中就能解決掉這些bug。這樣,每當(dāng)有錯誤漏過提交時,提交測試就會加強。在某些情況下,沒有一種快速運行的測試來暴露bug,因此你可能決定只在第二級構(gòu)建中測試該條件。幸運的是,大多數(shù)情況下,你可以向提交構(gòu)建添加適當(dāng)?shù)臏y試。這個例子是一個兩級流水線,但是基本原理可以擴展到任何后期階段。提交構(gòu)建之后的構(gòu)建也可以并行完成,因此如果有兩個小時的第二階段測試,則可以通過讓兩臺機器分別運行一半的測試來提高響應(yīng)速度。通過使用類似這樣的并行第二階段構(gòu)建,你可以將進(jìn)一步的自動化測試(包括性能測試)引入到常規(guī)構(gòu)建過程中。2.8 在生產(chǎn)環(huán)境的克隆中測試測試的重點是在受控條件下,清除系統(tǒng)在生產(chǎn)中可能出現(xiàn)的任何問題。其中很重要的一部分是生產(chǎn)系統(tǒng)運行的環(huán)境。如果你在不同的環(huán)境中進(jìn)行測試,每一個差異都會導(dǎo)致風(fēng)險,即在測試中發(fā)生的事情不會在生產(chǎn)中發(fā)生。因此,你希望將測試環(huán)境設(shè)置為盡可能精確地模擬生產(chǎn)環(huán)境。使用相同版本的數(shù)據(jù)庫軟件,使用相同版本的操作系統(tǒng)。將生產(chǎn)環(huán)境中的所有適用的庫放入測試環(huán)境中,即使系統(tǒng)實際上沒有使用它們。使用相同的IP地址和端口,在相同的硬件上運行它。事實上,這是有限度的。如果你正在編寫桌面軟件,使用不同人員運行的、并安裝了所有的第三方軟件的桌面克隆中,進(jìn)行測試那是不現(xiàn)實的。類似地,有些生產(chǎn)環(huán)境的復(fù)制成本可能高得令人望而卻步(盡管我經(jīng)常遇到不復(fù)制中等成本的環(huán)境而產(chǎn)生的浪費成本)。盡管存在這些限制,你的目標(biāo)仍然應(yīng)該是盡可能多地復(fù)制生產(chǎn)環(huán)境,并理解測試和生產(chǎn)之間的每一個差異所帶來的風(fēng)險。如果你有一個非常簡單的設(shè)置,而沒有許多笨拙的通信,那么你可以在模擬的環(huán)境中運行提交構(gòu)建。但是,通常需要使用測試替身(test double),因為系統(tǒng)響應(yīng)緩慢或不夠穩(wěn)定。因此,通常都有一個非常人工的環(huán)境來進(jìn)行提交測試,以提高速度,并使用生產(chǎn)克隆進(jìn)行輔助測試。我注意到越來越多的人對使用虛擬化來簡化測試環(huán)境的組合越來越感興趣。虛擬化的機器可以保存在虛擬化中的所有必要元素中。安裝最新的構(gòu)建和運行測試相對簡單。此外,這還允許你在一臺計算機上運行多個測試,或者在一臺計算機上模擬網(wǎng)絡(luò)中的多臺計算機。隨著虛擬化的性能損失降低,這個選項變得越來越有意義。2.9 任何人都能輕松獲得最新的可執(zhí)行文件軟件開發(fā)中最困難的部分之一是確保你構(gòu)建了正確的軟件。我們發(fā)現(xiàn),很難事先明確自己想要什么,也很難做到正確;人們更容易看到不太正確的東西,并說出需要如何改變。敏捷開發(fā)過程明確地期望并利用人類行為的這一部分。為了幫助實現(xiàn)這一點,任何參與軟件項目的人都應(yīng)該能夠獲得最新的可執(zhí)行文件并能夠運行它:用于演示、探索性測試,或者只是看看本周發(fā)生了什么變化。.這樣做非常簡單:確保有一個眾所周知的地方,人們可以找到最新的可執(zhí)行文件。在這樣的存儲中放置幾個可執(zhí)行文件可能是有用的。對于最新的可執(zhí)行文件,你應(yīng)該放置最新的可執(zhí)行文件以通過提交測試—如果提交套件相當(dāng)強大,那么這樣的可執(zhí)行文件應(yīng)該相當(dāng)穩(wěn)定。如果你正在使用定義良好的迭代來跟蹤一個流程,那么通常明智的做法是將迭代構(gòu)建的結(jié)束也放在流程里。特別是演示,需要的是讓人熟悉功能的軟件,因此通常為演示者知道如何操作的,而犧牲最新的一些事是值得的。持續(xù)集成就是為了溝通,所以你希望保證每個人都能很方便的看到系統(tǒng)當(dāng)前的狀態(tài)以及在系統(tǒng)上所做的改變。其中用于溝通的最重要的一個途徑是主線構(gòu)建的狀態(tài)。如果你使用Cruise,那么有一個網(wǎng)站可以展示給你看是否有個構(gòu)建正在進(jìn)行,以及最后一次主線構(gòu)建的狀態(tài)。很多團(tuán)隊喜歡將持續(xù)顯示和構(gòu)建系統(tǒng)連接起來,從而使它更顯而易見。通常用燈來表示:綠燈表示構(gòu)建成功,當(dāng)失敗的時候則亮紅燈。常見的一個設(shè)計是紅色和綠色的熔巖燈—不僅僅給出這些構(gòu)建狀態(tài)的指示,而且還指示已經(jīng)處于該種狀態(tài)多久了。紅色熔巖燈上的氣泡表明此次構(gòu)建已經(jīng)失敗很久了。每個團(tuán)隊都可以自己選擇構(gòu)建傳感器—當(dāng)然你的選擇也可以是很好玩的(最近我看到有人在用一只跳舞的兔子)。即使你使用手動的持續(xù)集成,可視化依然很重要。物理構(gòu)建機器的監(jiān)視器可以展現(xiàn)主線構(gòu)建的狀態(tài)。通常可以給正在構(gòu)建的人的桌子上放一個構(gòu)建令牌(再說一次,像橡膠雞這樣的蠢東西也是一個很好的選擇)。人們常常喜歡給構(gòu)建成功加上一點簡單的噪音,比如說鈴聲。當(dāng)然,持續(xù)集成服務(wù)器的網(wǎng)頁可以承載更多的信息。Cruise不僅能提供誰正在構(gòu)建,而且還能提供他們做了什么改變。Cruise還可以提供改變的歷史,以使得團(tuán)隊成員可以對當(dāng)前項目中最近的行為有更好的了解。我知道團(tuán)隊的領(lǐng)導(dǎo)喜歡用這些信息來了解團(tuán)隊成員在做什么以及保持對系統(tǒng)改變的感知。使用網(wǎng)站的另外一個優(yōu)勢是那些異地辦公的成員可以獲知項目狀態(tài)。一般來說,我傾向于積極緊密工作在同一個項目上的成員坐在一起,但是經(jīng)常還會有一些其他人員關(guān)心項目的相關(guān)事宜。同時對于組織將多個項目的構(gòu)建信息聚合在一起也是很有用的—可以為不同的項目提供一個簡單并且自動的狀態(tài)。好的信息展示不僅僅是電腦屏幕上那些。我最喜歡的一個信息展示來自一個使用持續(xù)集成的項目上。它在一個很長的時間里不能穩(wěn)定構(gòu)建。我們把一整年的日歷放到墻上,用一個方框表示一年中的一天。如果QA團(tuán)隊得到一個通過了提交測試的穩(wěn)定的構(gòu)建,他們會把一個綠色的貼紙放到這一天的方框里,反之則放一個紅的貼紙。隨著時間過去,日歷揭示了構(gòu)建過程持續(xù)改善的情況,直到綠色的方框越來越多的時候,日歷就消失了—因為達(dá)到了它的目的。為了做持續(xù)集成,你需要多個環(huán)境,一個用來運行提交測試,一個或者多個用來運行第二階段測試。因為你每天都要在這些環(huán)境之間移動可執(zhí)行文件很多次,所以你希望能自動的做這些事兒。很重要的是你要有一些腳本可以很容易的把應(yīng)用部署到任意環(huán)境中去。這么做很自然的結(jié)果就是你還應(yīng)該有腳本,以使得你可以同樣簡單的部署到生產(chǎn)環(huán)境。你可能不會每天都部署到生產(chǎn)環(huán)境(雖然我曾經(jīng)參與過這樣的項目),但是自動化部署可以幫助你提高速度并且減少錯誤。這同樣是一個便宜的選項,因為這只是使用了你用于部署到測試環(huán)境的相同資源。如果你在生產(chǎn)環(huán)境自動部署,那么你需要考慮的一個額外的功能是自動回滾。糟糕的事情隨時會發(fā)生,如果發(fā)臭的棕色物質(zhì)撞到旋轉(zhuǎn)的金屬上(情況不妙),最好能夠很快的回到最后一個已知的好的狀態(tài)。能夠自動回滾,可以大大減少部署產(chǎn)生的緊張感,鼓勵人們更頻繁的部署,因此我們可以更快的把新特性提供給用戶。(Ruby on Rails社區(qū)開發(fā)了一款名為Capistrano的工具,是做此類事情的工具的一個好例子)在集群環(huán)境中我看到滾動部署,新的軟件會一次部署到一個節(jié)點,然后在幾個小時內(nèi)慢慢將整個應(yīng)用替換掉。對于面向大眾的網(wǎng)站應(yīng)用,我接觸到的一個很有趣的部署方式是:將一個試用版本部署給部分用戶。接著團(tuán)隊可以觀察該試用版本是如何被使用的,然后決定是否將其部署給全量用戶。這使得你可以在做出最終選擇之前測試新特性和用戶接口。自動化部署結(jié)合良好的持續(xù)集成原則,是此工作的重要基礎(chǔ)。
總起來說我認(rèn)為持續(xù)集成最顯著也是最寬泛的好處在于減少了風(fēng)險。我又想起了在本文第一段中提到的軟件項目。團(tuán)隊已經(jīng)處于一個漫長項目的末期(起碼他們是這么認(rèn)為的),但是還沒法判斷距離項目真正做完還有多久。
推遲集成的麻煩在于很難預(yù)測到底需要多久來做這件事兒,并且更糟糕的是甚至很難看到你在這個過程中的進(jìn)展。結(jié)果就是,即使你是少數(shù)幾個還沒延遲的案例之一,你也會在項目最緊張的部分陷入完全的盲區(qū)。
持續(xù)集成完全可以解決這個問題。沒有了長期的集成,你就可以完全的消除盲區(qū)。在所有的時間點上你都會知道你身處何處,什么可以工作,什么不能工作,你的系統(tǒng)里有什么最大的bug。
Bug—也就是那些討厭的東西,會摧毀我們的信心,破壞我們的計劃和聲譽。如果已經(jīng)發(fā)布的軟件中有缺陷,用戶就會對你很生氣。而正在進(jìn)行的工作中有bug,則會擋住你的道路,會使得接下來的工作變得更加困難。
持續(xù)集成不會避免bug,但是它可以讓你非常容易找到并消除bug。從這個角度看它很像自測代碼。如果你引入了一個bug后能很快檢測到它,那么就能很容易改正它。因為你只是對系統(tǒng)做了一個很小的改變,你不用往回追溯太遠(yuǎn)。因為系統(tǒng)的那一部分正是你剛剛工作過的那一部分,它還很清楚的存在于你的記憶里—這使得查找bug很容易。你還可以使用diff debugging,來對系統(tǒng)的當(dāng)前版本和沒有bug的更早的版本作比較。
Bug也是累積的。你的bug越多,消除每一個bug也就越不容易。一定程度上是因為bug互相影響,表現(xiàn)出來的失敗可能是很多錯誤共同疊加的結(jié)果—這就導(dǎo)致很難找到每一個錯誤。這也是心理上的,當(dāng)有很多bug的時候,人們自然就缺少激情去找到并解決這些bug—這是一種在《Pragmatic Programmer》一書中被稱為“破窗效應(yīng)”的現(xiàn)象。
因此,使用了持續(xù)集成的項目,無論是在生產(chǎn)環(huán)境里,還是在開發(fā)過程中,其bug的數(shù)量將會極大的減少。但是需要強調(diào)的是,受益的程度直接取決于你的測試套件的好壞。你應(yīng)該可以看到,構(gòu)建一個帶來顯著差異的測試套件并不是那么困難。當(dāng)然,通常一個團(tuán)隊真正達(dá)到他們可能達(dá)成的較低水平的bug程度,需要一定的時間。要達(dá)到這個水平意味著需要持續(xù)的改善你的工作。
如果你使用了持續(xù)集成,它會消除掉頻繁部署的一個最大障礙。對于能夠為新特性更快的獲得反饋來說,頻繁部署是有價值的,因為它可以使你的用戶很快用上這些新的特性,而且頻繁部署可以使他們(開發(fā)團(tuán)隊和用戶)在開發(fā)周期中更加協(xié)作。這將有利于打破客戶和開發(fā)之間的障礙—我認(rèn)為該障礙是成功的軟件開發(fā)中最大的障礙。
所以如果你想要嘗試下持續(xù)集成的話,從哪兒開始呢?上面我提到的所有的實踐可以帶給你全部的收益,但是你并不需要一開始就把它們都用上。
這兒其實并沒有固定的套路,而是很大程度上依賴于你的配置和團(tuán)隊的現(xiàn)狀。但是我們也學(xué)到下面的一些經(jīng)驗可以幫助我們把接下來要做的事情搞清楚。
第一步就是把構(gòu)建自動化。把你所有需要用到的東西都放到源碼控制系統(tǒng)中,從而你可以用一條指令來構(gòu)建整個系統(tǒng)。對于很多項目來說,這并不是一件小事——但是它是其他所有的事情的基礎(chǔ)。一開始的時候你可能只是偶爾按需構(gòu)建,或者只是做自動的每夜構(gòu)建。當(dāng)還不是持續(xù)集成的時候,自動的每夜構(gòu)建也是一個很好的開始。
在你的構(gòu)建中引入一些自動化測試。嘗試著識別出問題最多的部分,并且加入自動化測試以暴露這些問題。需要特別指出,在一個現(xiàn)存的項目上非常快的實現(xiàn)一個真正好的測試套件是很困難的—因為需要時間來構(gòu)建測試。你必須從某個地方開始—畢竟羅馬不是一天建成的。
盡量加速提交的構(gòu)建。構(gòu)建需要幾個小時的持續(xù)集成也比沒有持續(xù)集成要強,但是如果能將這個時間降到10分鐘那就太好了。這通常需要對你的代碼基礎(chǔ)做一些相當(dāng)大的手術(shù),因為你需要打破對于系統(tǒng)中較慢部分的依賴。
如果你開始一個新的項目,那么從一開始就用持續(xù)集成。一直關(guān)注構(gòu)建時間,一旦構(gòu)建速度變慢,超過10分鐘的時候,馬上采取行動。通過很快的采取行動,你可以在代碼變得太大以至于成為主要的痛點之前進(jìn)行必要的重構(gòu)。
上面所有的這些都可以給我們提供一些幫助。找到一個以前做過持續(xù)集成的人來幫助你。和任何新技術(shù)一樣,當(dāng)你還不知道最終結(jié)果會是什么樣子的時候,很難引入該技術(shù)。找一個導(dǎo)師來可能會花一些錢,但是如果不這樣做,你將不得不付出大量的時間和生產(chǎn)效率。(免責(zé)聲明/廣告—是的,我們ThoughtWorks在該領(lǐng)域提供顧問工作,畢竟我們已經(jīng)犯過大多數(shù)你們將要犯的錯誤。)
在Matt和我寫完這個網(wǎng)站上最初的論文后的幾年中,持續(xù)集成變成了軟件開發(fā)的一個主流技術(shù)。ThoughtWorks的項目中很少有不用它的—并且我們可以看到遍布全球的很多人也在使用持續(xù)集成。不像一些有爭議的極限編程實踐一樣,我很少聽到關(guān)于持續(xù)集成的負(fù)面反饋。
如果你沒有正在使用持續(xù)集成,那么我強烈建議你嘗試一下。如果你正在使用持續(xù)集成,那么這篇文章里的一些想法可以幫助你做得更有效率。在過去的幾年里,關(guān)于持續(xù)集成我們已經(jīng)學(xué)習(xí)了很多,我希望依然有更多的東西可以學(xué)習(xí)和改善。
延伸閱讀
一篇像這樣的短文只能覆蓋這些基礎(chǔ),但是這是一個很重要的主題,所以我在我的網(wǎng)站上創(chuàng)建了一個導(dǎo)航頁面,可以給你更多的信息。
如果想更多的了解持續(xù)集成,我建議看看Paul Duvall的書《Continuous Integration: Improving Software Quality and Reducing Risk》(該書獲得了Jolt大獎—比我想象的還要厲害)。關(guān)于更廣泛的持續(xù)交付過程的更多信息,可以看看Jez Humble和Dave Farley的書,該書也獲得了Jolt大獎。
你也可以在ThoughtWorks的網(wǎng)站上找到更多關(guān)于持續(xù)集成的信息。
IDCF DevOps黑客馬拉松2020年再度開跑,北京、深圳、西安、上海四城報名通道已經(jīng)開啟!打造端到端的DevOps教練,快掃碼報名加入吧~關(guān)注本公眾號回復(fù)“黑馬”——>將指定鏈接轉(zhuǎn)發(fā)到朋友圈,并集贊50個——>截圖發(fā)給小柒,即可參與抽獎,中獎?wù)呖蓽p2000元報名費哦~