持續集成精華理論

持續集成是一個程序開發的原則,它要求開發小組的每個成員頻繁的集成他們的工作成果,這個頻度通常是至少每天一次,有時甚至每天多次。每次的集成通過一個包含測試的build去儘快的探測潛在的錯誤。很多團隊都發現這種原則能有效地減少集成問題,並能讓他們更快的開發出cohesive(粘連性)的軟件。這篇文章對持續集成的技術和用法做了一個快速的總結。

我清楚的記得我參與第一個大的軟件項目的情形:當時我在英國一家大型電子公司做暑假實習生,我的經理,QA組的一員,給了我一個tour的職位,我和他一起進入了堆滿立方體的倉庫,我被告知這個項目已經開發了幾年了,現在正在集成,而且已經集成了幾個月了,我的嚮導告訴我沒人知道集成完成還需要多久。從這件事情上,我學到了軟件項目的一個通用的道理:集成是一個漫長且不可預測的過程。

但是好像不是那麼回事。我在ThoughtWorks的同事們做的大多數項目(還有世界各地的人蔘與)根本就不把集成當成一回事。每個獨立的開發者的工作可能每次就幾個小時,對於一個共享的項目來講,幾分鐘內就能把他們的工作集成進來,每次集成的錯誤很快就能發現並被修復。

這個相反的事實並不是使用一個昂貴而且複雜的工具的結果,它的本質依賴於在開發組的每個組員都遵循了一個簡單的原則:在有代碼管理的倉庫中頻繁的進行集成。

當我把這個原則剛開始講給人們聽時,反饋一般是:你那套在我這沒用這樣做也沒多大用。但當人們發現持續集成比聽起來簡單,而且給開發帶來的巨大變化以後,不同的反饋就來了:當然,我們當然這麼做了,不敢相信你離開它還能活下去嗎?

術語持續集成起源於極限編程開發,是它的12個基本原則之一。當我作爲一個顧問在ThoughtWorks開始工作時,我就鼓勵我工作的項目組使用這項技術。Matthew把我的建議徹底的貫徹到了實踐中,我們的項目隨着持續的進行,逐漸進入了我前面描述的把集成不當一回事的境界了。Matthew和我把我們的經驗寫下來形成了這邊文章的初始版本,它已經成了我的網站裏面最受歡迎的文章了。

雖然持續集成只是一項不需要特殊工具去實施的一個原則,但是我們發現使用持續集成的服務器以後會非常有幫助。最出名的服務器是CruiseControl,由ThoughtWorks的幾個人構建的一個開源工具,現在在社區裏廣泛流傳。最初的CruiseControlJAVA寫成的,現在爲Microsoft的平臺開發的CruiseControl.NET也開始使用了。

1 持續集成(CI)的特性

解釋CI是什麼,怎樣去使用它,最好的方式是:給大家展示一個使用持續集成的原則進行開發的例子。假設我必須得爲軟件的一部分做些事情,不論任務是什麼,同時假設任務很小,幾個小時內就能完成(我們稍後會研究更大的任務和其它主題)。

我先獲取了一份當前集成的代碼到我本地的開發機器上,我使用了代碼管理工具的檢出就辦到了。

我假設大家都用過代碼管理工具,不然你就對上面的一段就不理解了。代碼管理系統把一個項目所有的源代碼都保存在一個倉庫裏。系統的當前狀態始終是最新提交的代碼,我們叫它主線(mainline,任何時候開發者都能把這個主線上的代碼更新到自己機器上,我們叫它檢出。開發者機器上的代碼叫做工作拷貝(working copy)。

現在我獲得了我的工作拷貝並在這上面來完成我的任務:改變產品代碼,添加和更改自動測試。持續集成假設了一個軟件中的高度自動化測試機制:我叫它自測代碼。他們通常使用一個Xunit測試框架的版本。

一旦我完成(或在工作過程中的任何一點),我就在我的開發機器上執行一個自動化的構建(build):它能把我的代碼編譯,鏈接成可執行,並運行自動化測試。只有所有的編譯和測試都沒有錯誤,我們就認爲是成功的構建了。

構建成功後,我就可以考慮把我做的工作提交代碼倉庫了,但是同時有可能其他人也會把工作提交到主線,所以,我得先更新我的工作拷貝然後再重新構建。如果我的工作和主線的代碼有衝突的話,那麼在編譯或測試的時候就會報錯。在這種情況下,我就得去修復這個問題,直到我能將我的工作拷貝同步的構建成功。

一旦我的工作拷貝構建成功我就可以提交更新倉庫的。

但是我的提交併不意味着我完成了我的工作,我還得需要把主線的代碼進行構建,因爲我有可能把有些工作遺忘到我的機器上,而倉庫沒有得到正確完整的更新。只有當我的工作提交後的代碼能夠成功的構建,我的工作纔算完成。集成構建的工作可以是我手工的執行,也可以是用CruiseControl來自動執行。

如果二個開發者之間的工作衝突,通常會在第二個開發者提交工作拷貝時發現。在這個時候最重要的事情就是去修復這個衝突,直到集成構建成功。你絕對不能把這種衝突存放很久,一個好的團隊會每天都確保自己的集成構建正確,即使有失敗發生,也會很快修復。

2 持續集成的原則

上面講的只是CI的簡單描述,要想能很順利的工作還需要注意更多。我們下面主要集中來說說有效的CI的幾個關鍵原則。

2.1 維護唯一的代碼倉庫

一個軟件會包括很多文件,需要把他們像音符一樣編制在一起才能生成產品。要想把握全局需要花費很大的氣力,特別是當這個項目有很多人蔘與時。所以那些開發時間較長的項目團隊都會有構建工具去管理項目。這些工具有代碼管理工具,配置管理,版本控制,倉庫,還有各種各樣的名字,它們已經是項目的一個完整的組成部分。

所以一個基本的原則是確保你有一個最近版本的代碼管理系統。成本不再需要考慮,因爲開源工具已經唾手可得。當前一個可選的開源倉庫是Subversion。(老版本的是CVS,雖然還廣泛使用,但Subversion更摩登一些),商業的代碼管理工具我聽得多一些的是Perforce

一旦你有了代碼管理工具,你就要告訴大家怎麼通過它去獲取代碼。不要出現有人問某某文件在哪兒的問題,什麼東西都應該在代碼倉庫裏。

雖然很多項目組都使用代碼倉庫,但一個常見的錯誤是他們沒有把什麼都放在倉庫裏。如果大家使用倉庫,就應該把用於構建的東西都放到裏面去:測試腳本,屬性文件,數據庫,安裝腳本和第三方庫。我還知道有人把編譯器都檢入(check in)倉庫的。基本的原則是,對於一個處女機,只要對倉庫執行檢出,就能構建一個完整的開發平臺。(處女機上只需一些較大的,複雜的軟件如操作系統,JAVA開發環境,數據庫等)

你最好把構建所必須的東西都放進資源控制系統裏,你也可以放些其他開發者經常會用到的東西,IDE setups也可以放進去,這樣大家可以共享同一個IDE了。
版本控制系統的一個特性是能讓你創建多個分支去出來不同的開發流。這是個十分有用的特性,但是過分使用的話會把大家帶入困境。儘量讓分支小。特別的是要有一個主線:一個唯一的程序分支,每個人都從這個主線開始工作(分支往往會是先前一個已發佈版本的bug修復版或臨時的試驗版)。

2.2 自動構建

讓現有代碼形成一個運行的系統通常是個很複雜的過程:編譯,移動文件,裝載表到數據庫等等。但是大部分的工作都可以自動化,也需要自動化。要人們去敲陌生的指令,或是點擊對話框是個費時又易出錯的事情。

自動化的構建環境是通用系統的一個特性,Unix世界已經使用make數十年了,JAVA社區開發的ANT.NET社區有Nant,現在又有了MSBuild。確保你能通過一個簡單的命令調用這些腳本來構建和啓動你的系統。

一個常見的錯誤是沒有把所有的事情都包括到自動化構建中去。構建應該包括把數據庫表從倉庫中取出然後更新到執行環境中。我又要引用我之前的規則:每個人在處女機上只要通過一個簡單的命令就能檢出代碼並獲得一個可運行的系統。

構建腳本有許多種,根據不同的平臺,社區會有不同,雖然我們JAVA項目通常用ANT,但是也有人用RubyRuby Rake系統是個很好的構建腳本工具)。

一個大的構建通常十分耗時,但是如果你只是做了一個小的改動,你不必全部重新構建。所有好的構建系統能分析什麼需要去改變。通常的方式是檢查代碼或目標對象的日期,只編譯那些日期更新過的。這依賴於他們的機制,如果一個目標文件改變了,那麼那些與之相關(或依賴它)的對象也要重新構建。有的編譯器能處理這種情況,有的則不能。

根據你想做的事情,你可以構建一個系統帶或者不帶測試代碼,或者有不同的測試集。一些組件能單獨的構建,一個構建腳本應該能根據不同情況提供可選的目標。

我們有很多人都使用IDE,大多數IDE都有一些代碼構建的工具內嵌其中。然而這些工具總是依賴於IDE而且很脆弱,如果那些沒有IDE的開發者就可能沒辦法進行了。最好還是用一個服務器來使用ANT來確保構建的進行。

2.3 讓你的構建能自測

構建,傳統意義上意味着編譯,鏈接和一些使程序能執行的工作。一個程序能運行,但不意味着它能做正確的事情。

獲取bug的好的方式是在build過程中包含自動測試。測試不可能完美,但是它確實能捕獲bug,這就夠了。實際上,極限編程和測試驅動開發已經爲自測代碼的推廣樹立了好的榜樣,越來越多的人看到了這項技術的價值。

經常看我文章的人都知道,我是極限編程和測試驅動開發的忠實fans,然而我要強調的是:不是所有這些方法都能獲得自測代碼的好處。這些方法都強調在你編碼之前寫測試,然後讓你的編碼能通過這些測試,在這種模式下,測試工作跟設計系統所做的工作一樣多。這是個好事情,但是對於持續集成的目的來說不是必要的,我們沒有那麼多的自測代碼需求(儘管測試驅動開發是我生成自測代碼的偏愛的方式)。

對於自測代碼,你需要一套自動測試來檢查大段代碼中的bug,這些測試需要能被從簡單的命令中剔除和能夠自測。測試的結果能夠指示錯誤的所在。自測的失敗會造成build的失敗。

這些年來,測試驅動開發的廣泛使用讓開源工具XUnit family在測試中的使用也普及開來,Xunit工具在ThoughtWorks的工作實踐中已經證明非常有用,我總是建議人們去使用它們。就因爲這些工具(Kent Beck是我們的先導)使我們能很輕易的構建一個完整的自測環境。

Xunit工具肯定是你進行代碼自測的開始點。你也應該看看其它的一些更注重end-to-end測試的工具:FIT, Selenium, Sahi, Watir, FITnesse等等。

當然你別指望測試能找到一切。有句話叫做:測試不能證明BUGS不存在,完美的測試總是不能寫出來,而不完美的測試卻可以執行多遍幫你去找到錯誤。

2.4 每個人每天都提交代碼

集成主要是溝通,它能讓開發者知道彼此所作的改動。

開發者提交代碼的前提是他們能把自己的代碼正確的build,當然也包括build中的測試。提交的過程是:先把自己的工作拷貝與主線同步,然後找到並解決其中的可能的衝突,然後在自己本地機器上build,如果能夠通過的話,就可以自由的提交代碼到主線了。

通過頻繁的這樣做,開發者能很快的發現二個開發者間的衝突。解決這個問題的關鍵就是儘快的發現衝突。當開發者幾小時提交一次,那麼衝突就能在幾小時內發現,這個時候開發者可能還沒走遠,問題就容易解決了。而且這幾個小時內變動的地方有限,bug的藏身之處也就容易發現了。你可以用diff-debugging去幫你找到bug

我的原則是每個開發者每天都得提交他們的代碼,當然如果大家提交的比這更頻繁,那麼衝突也就更容易發現了。但通常人們發現自己無法在幾小時內就完成一項有意義的變動。

2.5 每次提交都在主線集成的機器上做build

使用每日提交,團隊會頻繁的進行測試build。如果大家在提交前沒有更新就做了build或者開發者的機器環境不一樣的話,大家build的結果會不一樣。所以你得在集成了主線的代碼的機器上去做build,這樣你就需要一個持續集成的服務器。

持續集成的服務器就像個數據倉庫的監視器,每次的代碼提交都會自動的觸發checkout代碼到集成服務器上,初始化build,並將結果通知提交者。提交工作直到收到郵件通知纔算結束。

持續集成的引入會給大家的工作習慣帶來衝擊,大家會不習慣去總是留意主線的build狀態。

2.6 build快點

持續集成的要點是提供快速的反饋,沒有什麼比build耗時更能吸CI活動的血了。但是對於什麼是耗時的build,這個概念很難界定,根據極限編程的原則,10分鐘應該是個好的分界線了。大多數好的項目都不會超過這個時間,因爲此時每節約1分鐘就爲全體開發人員每人都節約了一份鍾。既然集成build要經常的做,那麼累積起來的時間就更多了。

大多數的build都是時序的進行的,提交觸發一個主線的build,然後由它來依次的調用其他的build。其實這其中耗時最多的還是測試,但是如果減少測試的話,雖然能提高build速度,但探測bug的能力就下降了。

一個好的方法是進行一個二步的build,第一步編譯並在本地進行單元測試,這樣的測試一般很快,不會超過10分鐘的原則。但是那些參雜在內部交互中的bug就很難發現了。第二步是運行一個不同的測試集(針對真實的數據庫)的build,這個可能比較費時了。

這種情況下,大家可以把第一步作爲提交build,把這作爲主CI循環。第二步build是個次要的build,它能把最新的好的提交build檢出來進行測試。如果第二步的build失敗,並不停止一切,而是讓開發小組儘快的修復這個失敗,同時保持其他的提交build繼續運行。關鍵的問題是這二步的同步。

這只是個二步build的例子,其他多步build的例子道理是一樣的,只要遵循同步和迅速修復錯誤的原則,完全可以實現多步build的同步。

2.7 隆一個產品環境進行測試

因爲最終的程序會部署到產品環境中去,因此在不同的環境下進行測試的話,會存在一些因環境變化造成失敗的風險。

這樣你就得找個同產品環境裝了一樣的操作系統,同樣版本數據庫,同樣版本的第三方類庫(即使你不會真的用到它)的環境,使用同樣的ip地址和端口,運行在同樣的硬件配置上。

也許有時這種產品環境的複製很是花費,或者根本不可能完全做到,但你應該儘可能的去使用這條原則。

2.8 讓每個人都能容易的獲取最新版的可執行程序

軟件開發一個很重要的部分是你要確信你build了正確的軟件。人們往往很難提前知道他需要什麼,什麼需要被改正,但是往往問題出現時,他才知道改變是多麼需要。敏捷的開發過程詳盡的利用了人們的這種行爲。

爲了幫助工作,每個項目組的人都應該能夠去獲取一個最新版的可執行程序而且確保能運行:演示,探測測試,或是僅僅看看這周有什麼變動。

找個大家都知道而且能獲取最新版的項目的地方就是很明顯的事情了。也許把幾個版本的可執行程序放同一個地方會有很有用。這樣就可以方便比較新開發的特性和提交不同的代碼版本了。

2.9 讓每個人都知道正在發生什麼

持續集成所做的一切都是爲了溝通,所以你要確保所有人都知道系統現在的狀態,目前已經做了那些變動。

溝通中最重要的一件事是主線的build狀態。如果你正在使用CruiseControl,這上面有個build,它會告訴你build的狀態和上次主線build的狀態。許多小組喜歡把這些消息更明顯化,build成功的話就是綠燈,失敗就出現紅燈。(lava lamps

如果你做一個手工的CI過程,這種可視化仍然是本質。CruiseControl提供了一個網站來指示:誰在build,他做了哪些改動。同時它也提供了項目小組系統最近變更的歷史記錄。小組的領導就喜歡拿這些東西來看是哪些人做了哪些事。

使用網站的另一個好處是:那些不在一起工作的人也能看到目前項目的狀態。我一般是喜歡所有圍繞一個項目工作的人都坐在一起,但是全球所有人有時都會把目光放在一些事情身上。

好的信息顯示不僅僅是在計算機屏幕上,你完全可以有其他的途徑。我們組曾經有個項目1個月都沒有1次成功的build了,我將一個大的日曆張貼起來,每次build通過就劃一個綠燈,失敗的話就劃個紅燈。這樣大家每天就很清楚今天該做的事情了。

2.10 自動部署

要做持續集成,你需要多個環境,一個運行提交測試,其它的運行次要測試。如果你需要每天把代碼在這些環境間搬來搬去,你就要把它自動化。擁有能讓你把應用部署到這些環境的腳本是十分重要的。

接下來的事情就是你也要有腳本能讓你迅速的把這些應用部署到產品環境中。你可能不必每天都升級產品環境,但是自動化的部署能讓你的工作加速而且減少錯誤。而且這也是一勞永逸的事情。

有可能的話你儘量讓你的自動部署支持部署回滾,這樣就可以鼓勵人們去進行更頻繁的部署,同時能加強系統的安全係數。Ruby on rails社區開發了一個工具叫Capistrano就是個好的例子。

回滾中最困難的事情是數據庫遷移,數據庫改變是件很恐怖的事情,你無法保證你的數據能正確的遷移。Evolutionary Database Design這篇文章詳細的描述了自動重構和數據庫遷移的技術,在它裏面它試着去獲取在Pramod and Scott Amblers的書裏介紹的重構數據庫的詳細信息。

自動部署還一個好處是能讓你在正式部署前做些測試試驗,讓你的用戶去體驗新的特性和新的用戶界面,由此來給你提交最好的決定做參考。自動化部署是CI的一個好的原則。

3 持續集成的好處

我認爲使用持續集成的最大的好處是減少風險。當我的思緒回到我前面提到的那個項目,他們的工作是快結束了,但誰也不知道他們什麼時候能真正結束。
不連續,不及時的集成的最大的問題是,很難預測項目要花多長時間,最糟的是,你不知道你究竟還要走多遠。結果就是,你想個盲人一樣置身於項目的大森林裏,雖然你是個很少迷路的人。

持續集成完全能解決這些問題,沒有漫長的集成工作,所有的盲點盡在掌握。無論什麼時候你都知道你在那兒,你的工作是什麼,問題是什麼,那些bug就跟蝨子矗立在光頭一樣那麼明顯。

持續集成不能完全擺脫bug,但它能讓你儘快找到並消滅它們。就因爲這個原因我鍾愛自測代碼。

Bug是累積的,bug越多你越難消滅它們。有時候bug是相互交叉的,多個bug在一起的時候,往往消滅一個,會帶出更多(拔出蘿蔔帶出泥)。有時候這也是心理問題,人們有時會對存在大量bug的代碼產生恐懼心理,這就是所謂的Broken Windows 綜合症(Broken Windows syndrome)。

持續集成的效果與你的測試集的質量是成正相關的,要找出那些很深層次的bug,意味着你得不斷提高你測試集的質量。

如果你使用持續集成,它就能幫你跨越頻繁的部署的障礙。頻繁的部署是很有意義的,它能讓你的用戶很快的發現系統新的特性,然後給你針對這些特性迅速的反饋,這樣就能讓你的開發循環有更好的協同工作。這就能讓你的用戶和你們的開發能更好的溝通——我認爲這是個成功的軟件開發中最大的障礙。

4 持續集成入門

也許你現在特別想試驗一下持續集成,那麼應該從那兒開始呢?我上面所列的是持續集成的所有好處,你不必一開始就都將它們使用進來。這兒沒有標準答案,更多的是依賴於你們的項目和開發小組。但是這兒有些事情我們可以學着去試試。

第一步是讓build自動化。把你所要的東西都放到代碼管理器中,這樣你就能用一個簡單的命令build整個系統。對於項目來講這不是一個鏡像,而是其他所有工作的本源。剛開始你可以偶爾的進行build,或者做自動的晚間build。也許一個晚間自動build是你實施持續集成的最好開始。

在你的build中引入自動化測試。試着標識出那些容易出錯的主要區域,然後把自動測試加入其中來檢測錯誤。特別的是在一個已經存在的項目當中很難快速的獲得一個測試用例,這很花費時間,但你能從一些小地方開始。

試着加速你的提交build,一個要花費數小時的持續集成build比沒有做持續集成好,但是能夠坐下來想辦法將build時間壓縮到10分鐘的話,那就再好不過了。這通常就需要你對你係統中較慢的地方所依賴的代碼做些漂亮的外科手術了。

如果你開始一個項目,那麼最好一開始就進行持續集成。留意你的build時間,迅速採取行動讓它儘量不要超過10分鐘。只有這樣才不至於讓你的系統變得龐大以後再來做減肥的工作。

找到那些曾經做過持續集成的人來給予你幫助。一項新技術是很難去解釋清楚的,你無法想象結果是什麼,儘管找個顧問會花費很多,但是如果你不這樣做的話你就會損失你的時間和生產效率。

5 最後的想法

自從Matt和我在這個網站上寫了那份原始版本的文章,持續集成已經成爲了軟件開發的主流技術。現在ThoughtWorks的項目沒有了它很難進行。我也看到世界各地的很多人都在使用CI。我很少聽到這種方法的副作用,至少不像極限編程的其他原則那麼多爭議。

如果你還沒有使用持續集成,那麼我強烈的建議你試一試。如果你已經在使用,那麼上面的一些觀點也許能幫助你將集成更有效的執行。在過去的幾年裏,我學到了很多持續集成的知識,我希望將來能有更大的提高。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章