持續集成

持續集成之戲說Check-in Dance

http://www.infoq.com/cn/news/2011/01/ci-check-in-dancehtml

【編者按】衆所周知,敏捷軟件開發方法中有多種最佳實踐,既有管理方面的,也有技術方面的。在嘗試敏捷之初,並非每一個團隊都能使用所有最佳實踐,也不是每一個實踐都能在短期內見效。但其中有一種最佳實踐倒是團隊的必選,那就是持續集成,但這並不表示持續集成很是容易。數據庫


儘管Thoughtworks的首席科學家Martion folwer 爲「持續集成 」下了定義,但因爲自身背景與經歷的不一樣,每一個人對其都有不一樣的理解。從狹義上講,持續集成能夠認爲是一種基於某種或者某些變化對軟件系統進行的常常性的構建活動(注:這裏的構建活動不只指編譯打包工做,還包含各種自動化測試、部署及發佈活動)。然而,它忽視了一點,即:任何實踐中都應該包含「與人的交互」這一因素。所以,從廣意上講,持續集成應該是軟件開發團隊在上述活動的約束下所採用的整個開發流程及活動。它強調開發團隊與持續集成系統之間的互動性。咱們既見過持續集成作得很是成功的團隊,也見過效果不佳的持續集成,甚至失敗的案例。服務器

那麼,到底如何從持續集成中獲得最大的收益呢?這要從持續集成所涉及的諸多方面進行分析,並根據團隊具體狀況(好比團隊規模、人員組成以及是否爲分佈式團隊 等)及所開發軟件自身的特色(是企業應用軟件,仍是中間件?是嵌入式軟件,仍是互聯網產品等)制定實踐策略與實現步驟。本專欄將與你們共同探討與持續集成、持續部署及持續交付相關的方法、工具與經驗。做者本人在Thoughtworks公司曾參與的一款持續集成與發佈管理產品Go的交付和對外諮詢服務爲專欄提供了頗有素材,同時感謝肖鵬 、 李彥輝 、胡凱 、李劍等對欄目內容的支持和幫助。網絡

在軟件開發中,持續集成實踐可以解決的問題是儘早的集成和儘早的反饋。所以,儘管目前流行的全部版本控制工具都提供了分支/合併功能,但在少於20人的團隊中實現持續集成的話,推薦使用Single Branch開發策略。這樣會減小多分支開如在合併時的開銷。另外,因爲理想狀況下,每一個分支都須要有專屬的持續集成環境(包括持續集成服務器、構建環境和測試環境等),因此Single Branch也減小了對持續集成環境的需求量(當編譯時間較長或測試用例較多時,這個因素的影響尤爲重要)。分佈式

當團隊完成最初搭建持續集成服務器,編寫好一鍵式編譯和測試腳本工做後,就須要考慮如何利用持續集成環境高效地進行團隊協做開發了。必定有人會問:ide

「多人同時在一個分支上開發的話,每一個人提交時都要合併代碼,不是更浪費時間嗎?」工具

這個問題也正是持續集成指望解決的問題。每當開發人員提交代碼時,就是其與其餘開發人員工做成果的一次集成。若是每一個人都可以頻繁提交代碼,那麼代碼集成的頻率就會提升,在持續集成的有力支持下,代碼中潛在的問題就會更早地暴露出來(好比代碼編譯連接問題,自動化測試失敗反映出來的代碼功能問題,或需求理解不一致等問題),以便團隊儘早解決之。單元測試

固然,持續集成所鼓勵的頻繁提交併非指那種僅將版本控制庫當成備份工具,無約束的「隨意」提交,還須要團隊開發流程約束的。下面咱們來一同探討「持續集成環境中的團隊開發流程是什麼樣的」。測試

讓咱們先設想一個軟件開發場景。ui

1、使用版本管理工具作備份

故事的主人公叫Joe,他打算寫一個遊戲,因此用Subversion創建了一個版本控制庫用於保存代碼,而後就動手寫代碼了。Joe的開發流程是這樣的。

  1. 從代碼庫中檢出一份代碼;
  2. 爲增長某個功能修改一些代碼;
  3. 在本地運行了一下自動化測試;
  4. 測試經過以後,提交代碼到版本控制庫;
  5. 重複前面的步驟。

如圖1所示。

2、搭建持續集成服務器作自動構建

「每次在本地手工運行自動化測試太麻煩了,」Joe想到,「這種重複的工做爲何不讓機器來作呢」。

因而,Joe上網查了一下,發現持續集成工具是作這個事情的,就找來一臺舊機器,用CruiseControl搭建了一個持續集成服務器。他的開發流程也變爲:

  1. 從代碼庫中檢出一份代碼;
  2. 開發新功能或修改bug;
  3. 提交到版本控制庫,思考下一個功能的實現;
  4. 持續集成服務器運行自動化構建和測試;
  5. 若是測試經過,轉到步驟(1);
  6. 若是測試沒有經過,轉到步驟(2)。如圖2所示。

3、多人並行開發

兩週後,遊戲初見原型,Joe向他的幾個朋友介紹了他的遊戲建立,他們都很是喜歡,所以也加入了遊戲開發。麻煩很快就出現了。持續集成服務器中構建結果常常失敗,因此每次檢出代碼後都要作問題清理工做。因而,Job與朋友們坐下來討論如何解決這個問題。

Alice說:「咱們每一個人都拉一個獨立分支,當每一個人的功能開發完成之後,再合併到一塊兒不就好了嗎?」

Joe不一樣意這樣的作法。「遊戲的需求還不明晰,要常常合在一塊兒看一下效果。因此仍是在同一個分支上開發吧。下面,咱們討論一下如何讓這種失敗少一些吧。」

因而,他們花了點兒時間,發現有兩個主要緣由致使失敗。

  1. 本地代碼有問題,本來就編譯不了或會致使測試失敗,但仍是提交了;
  2. 開始作新功能時,沒有特別注意分支上的持續集成狀態,直接將主分支上的代碼直接就與本地代碼合併了;

Joe提出,開發流程應該變成如圖3所示:

  1. 每一個人在開發新代碼以前,只能從持續集成已成功的那個最新版本檢出代碼;
  2. 開發新功能或修改bug;
  3. 提交前將主分支上的代碼再次取到本地合併;
  4. 運行本地測試,確保測試能夠經過;
  5. 提交代碼到主分支,由持續集成服務器再次運行測試。
  6. 若是測試經過,轉到步驟(1);
  7. 若是測試沒有經過,轉到步驟(2),直到修復持續集成上的構建。

但是,Alice提出反對意見。她認爲:「既然本地已經運行了測試,爲何還要在持續集成服務器上再次運行呢?」

Joe解釋到:「主要是由於咱們每一個人的本地環境都不徹底相同,極可能出現‘它在個人機器沒有問題呀’的這個現象,因此仍是要在獨立的持續集成服務器上再運行一次。」

所以,你們就這麼決定了。

4、兩次本地構建的目的

四周後的一天,Joe花了很長時間完成了某個新功能後,打算提交了。因而他把分支當前的代碼與其本地代碼進行了一次合併。而後運行了本地測試,但測試失敗了。他用了很長時間來定位該問題是在他本身修改的功能裏,仍是在被合入的代碼中。這讓他對提交流程進行了反思。

「要是在合入他人代碼以前,可以先運行一次本地測試,驗證一下個人代碼沒問題就行了,反正本地測試所花的時間也不長。」

因而,他把這個想法告訴了其餘人,最後大部分人都贊成這麼作。因而,其提交流程就變成了這樣:?

  1. 每一個人在開發新代碼以前,只能從持續集成徹底成功的那個最新版本檢出代碼;
  2. 開發新功能或修改bug;
  3. 運行本地測試,若是有失敗就當即修復,直至測試成本;
  4. 提交前將主分支上的代碼再次取到本地合併;
  5. 運行本地測試,確保測試能夠經過;
  6. 提交代碼到主分支,由持續集成服務器再次運行測試。
  7. 若是測試經過,轉到步驟(1);
  8. 若是測試沒有經過,轉到步驟(2)。

這個過程就被稱爲「Check-in Dance」。

Alice還說道:「咱們在從主分支上檢出代碼時,必定是那個經過持續集成驗證的最新版本。這樣能夠避免檢出的代碼就是有問題的,而影響本身本地的代碼。」整個過程如圖4所示。

5、持續集成令牌

過了幾天,有人把你們叫到了一塊兒,此次是Alice。她說:

「我今天遇到一個問題。我提交代碼以後,正等着持續集成服務器返回結果呢,Bob就提交代碼了。幸虧我提交的代碼經過了測試,不然的話,我就要在Bob的代碼之上修復啦。因此,我建議咱們須要設立一個提交令牌,只有拿到這個提交令牌的人才能提交。也就是說,當一我的作完本地測試以後,去拿這個令牌。拿到以後,再進行代碼合併、本地測試和提交。提交之後當持續集成服務器返回成功經過的結果時,才能交還令牌。這樣就不會出現我和Bob這種狀況了。」

可Bob並不一樣意這樣的作法,「此次沒有出什麼問題,爲何還要這麼作呢?」

此時,Joe把話接了過來,說道:「Alice的這個建議很好,我已經趕上過一次這樣的事情了,那次測試失敗之後,我花了很長時間才發現問題並不在個人提交中,而是在Mary的提交中。我把它修復後,又作了一次提交。」因爲大多數人都贊成這麼作,所以團隊決定試一試。由於目前測試運行時間很短,因此提交和集成工做沒有遇到什麼瓶頸。提交流程如圖5所示。

彷佛事情到這裏就結束了。然而,這個遊戲被某投資公司看中,決定作更大的投入,招更多的開發人員,讓它成爲一個開放遊戲平臺。那麼,接下來Joe與他的朋友們還會遇到哪些問題呢?

 

持續集成之「測試三角形與分段構建策略原則」

http://www.infoq.com/cn/news/2011/02/ci-test-triangle

隨着軟件產品新特性的不斷增長,軟件自動化測試用例的數量也會成倍增加。對於一些歷史「悠久」的遺留系統來講,甚至會積累數以萬計的自動化測試用例。若是對這樣的系統進行持續集成,還要求每一個開發人員都要進行本地驗證的話,困難的確不小。讓咱們仍是看看Joe的團隊是如何解決相似問題的吧。

在《戲說Checkin Dance》一文 中,我們說到:Joe?的團隊實施了帶有令牌的持續集成提交流程紀律。因爲每一個人都作本地構建進行驗證後再提交,因此持續集成平臺上的構建結果比較穩定,天天持續集成服務器上的構建最多隻有 一兩次失敗(常見的緣由是忘記提交某個文件而致使失敗,和因本地環境配置與平臺環境配置不一致而致使失敗),但通常都能在30分鐘內修復。隨着項目的進 行,新功能不斷地增長,自動化測試用例也越積越多。因爲不作任何修改,本地構建腳本就會運行全部自動化測試用例,因此本地構建的運行時間也愈來愈長。團隊裏有人開始抱怨,「每次提交代碼前,運行本地構建都超過15分鐘,這樣太浪費時間。咱們能否把那些不過重要的測試拿出去,再也不運行了?」

1、自動化測試黃金三角形

做爲團隊的技術負責人,Joe把你們叫到一塊兒,就這個問題進行了專門的討論。

「咱們不能放棄運行這些測試。」Alice說道,「在我前一個項目中,咱們就是這麼作的,結果,這些花精力寫的測試都做廢了。」

「那是爲何呢?」?Bob問道。

Alice回答道:「由於並不常常運行這些測試,隨着功能的修改,有些測試的邏輯就再也不是正確的了。而當再次運行發現這類問題時,一般的結果就是把這個測試刪掉,由於修復這個測試的工做量太大了。」

「那持續運行全部測試的話,等待的時間太長了,也是一種浪費呀。?」Bob說道。

此時,做爲團隊技術負責人的Joe說話了。「讓咱們先分析一下,到底有哪些什麼緣由讓咱們的測試在這麼短的時間裏就變成須要這麼長時間了呢?」

「功能增長的多了,測試天然就多了唄。」

「功能增長了,自動化測試數據的準備工做也多了,須要的時間固然就長了。」

「如今咱們的測試中有不少地方須要測試在原地等待結果返回,因此等待時間也挺長的。」

「你們還有沒有其它緣由?」Joe追問道。

你們沉默了一下子,Bob說道:「好象主要就這些緣由吧」。

「那好吧。功能多而致使測試多這是好事兒,說明咱們你們都很是重視咱們的自動化測試。對於‘測試準備時間變長’這個問題能夠理解,由於咱們的產品愈來愈複雜了。對於‘結果返回的等待問題‘嘛,須要具體問題,具體分析。前幾天,我看到一個‘測試黃金三角形’,講的就是自動化測試中各種測試的應具備的比例關係,對我頗有啓發。我在白板上畫一下吧。」因而,Joe走到白板前,將這個測試黃金三角形畫了下來,如圖1所示。

而後,Joe將這個圖形解釋了一下。原來,這個三角形講的就是單元測試、集成測試和驗收測試的關係。首先,左邊向上的箭頭表示,越高層次的測試維護成本越高,運行時間越長。所以,對於單個測試來講,單元測試運行最快,維護最容易,而集成測試次之,驗收測試則最高。?每類測試的面積表明着該測試的數量。如今,業界有不少種工具支持單元測試,所以它的編寫及維護成本相對其它兩種測試來講較低,應使用單元測試對代碼作儘量多的測試覆蓋。通常來講,單元測試覆蓋率達到70~80%是比較理想的狀態。

接着,Joe問了你們一個問題:「咱們產品中的這些自動化測試屬於哪一類測試?」

Alice說道:「那要看你怎麼定義單元測試中的這個單元。」

「根據WikiPedia上的定義,一個單元是指應用程序中最小可測試的部分。既然咱們使用面向對象的開發語言C++,那麼單元測試的粒度應該是類中的一個方法吧。並且,一般來講,若是一個測試包括如下任何一個情形,它就不是一個單元測試:(1)須要鏈接數據庫;(2)須要網絡通訊;(3)須要與文件系統打交道;(4)不能和其它單元測試同時運行;(5)須要對環境進行一些配置(如編輯配置文件)才能運行它。」Joe回答道。

「要是這麼說的話,咱們的測試中,一部分是模塊集成測試,一部分是驗收測試,只有一小部分算是單元測試。咱們的測試集合正好是一個倒三角。」Bob邊說,邊在白板上畫了出來,如圖2所示。

「既然高層次上的測試(集成測試和驗收測試)維護量比較大,從此咱們應該加入更多的低層次測試(單元測試),對於關鍵功能進行集成測試和驗收測試。若是對於測試用例具備等價性的話,咱們應該用低層次測試來實現。這樣咱們就會達到自動化測試的黃金三角狀態啦。」Joe邊說邊在白板上筆劃着,如圖3所示。

「我贊成你說法,可是仍舊沒有解決咱們目前遇到的問題。如何解決咱們如今本地構建時間太長的問題呢?」Alice有點兒不耐煩地問道。

2、分階段構建?

「這還不容易,Martin Folwer(敏捷宣言的創造者之一)已經給出了一個解決方案,那就是兩階段構建(Secondary Build)。也就是說,咱們能夠把那些運行比較慢,時間比較長且基本上不會失敗的自動化測試用例挑選出來,組成一個新的測試集,在第二階段運行,能夠叫作‘二級構建階段’。剩餘的測試集仍舊放在第一個階段運行,咱們能夠把第一個階段叫作‘提交構建階段’。」Joe回答道。

「那什麼時間運行這兩個階段的構建呢?」Bob問道。

「提交階段構建固然就是在咱們每一個人提交以後就運行啦。並且在咱們提交以前,做爲本地驗證集合,在咱們開發環境上也要運行一樣的提交構建。通常來講,本地構建和提交構建最好都在五分鐘內完成,最長也不要超過十分鐘,不然開發人員就不肯意花時間作頻繁地代碼提交啦。另外,一旦提交階段構建成功之後,就立刻自動觸 發第二階段構建。而咱們開發人員在持續集成服務器上的提交階段構建成功之後,就能夠繼續進行其它的工做啦。」Joe說道,「咱們原來的六步提交圖就變成這 個樣子了。」說着,Joe拿起白板筆就畫了出來,如圖4所示。

「不對,這裏有問題!持續集成強調儘早反饋。若是把測試分紅兩個階段了,那反饋週期不是加長了 嗎?」Bob反駁道。

Joe 點點頭,說道:「你說的沒有錯。可是,根據咱們現有的軟硬件資源條件,咱們目前還沒法經過增長資源的方式來縮短全部測試運行的時間。因此咱們必須在質量與速度以前作出平衡。這也是我爲何要把那些不易出錯的自動化測試集合放在第二階段構建的緣由,這樣能夠下降但不能徹底解除第二階段構建失敗的風險。因此, 這也要求咱們你們當第二階段構建失敗時,也要找人儘快把它解決,而且把相關的測試再次放回提交測試階段中運行,或者在提交測試階段加入新的測試來補充。」 ?

Alice此時插話,問道:「既然第二階段構建不常失敗,爲何咱們不定時運行它,好比天天晚上運行一次呢?這樣不是更節省資源嗎?另外,若是第二階段構建運行得慢,那它不是一直都落後嗎?」

「由於每次提交階段構建成功之後就觸發第二階段構建,這樣不管如何都比天天晚上運行一次的更多的反饋。由於天天晚上運行一次的話,若是出了問題,咱們只能在次日早上才能發現。對於你的第二個問題,我畫一張圖來解釋。」Joe找了一張大白紙,在上面開始畫了起來。

一下子功夫,幾個示意圖就畫好了。看到這幾個示意圖之後,你們恍然大悟。如圖5所示。從圖中咱們能夠看到:

  1. 當版本123的第二階段構建被觸發並正在運行,Alice又提交了一次,觸發了版本124的提交構建;
  2. 當版本124的提交構建完成以後,因爲版本123的第二階段構建仍在運行,因此再也不觸發第二階段構建;
  3. 當版本125的提交構建完成時,版本123的第二階段構建仍舊在運行,因此也不觸發第二階段構建;
  4. 當版本126提交構建正在運行時,版本123的第二階段構建剛完成,此時因爲版本125的提交階段構建是一個最近 成功完成的提交構建,因此持續集成服務器就會啓動該版本的第二階段構建,而忽略版本124的提交構建。

「那根據咱們持續集成紀律,誰的提交讓構建失敗,就由誰來修復。若是版本125的第二階段構建失敗了,就包括版本124和125兩次提交的變動,由誰來修復呢??」Bob接着問道。

「這個好辦,由這兩個提交人一塊兒負責修復。若是想確切找到誰的提交有問題,還能夠手動觸發版本124的第二次構建。假如構建成功,說明版本125有問題,假如構建失敗,說明問題在版本124就引入了。」Alice搶着說道。

討論到這裏,團隊成員都達成了共識,(1)開始增強單元測試的力度;(2)在反饋速度和反饋質量之間作出折衷,使用二級構建構建的方式。

整個產品的開發很是順利,立刻就要進行版本發佈了。團隊還會遇到什麼問題呢?他們是如何解決的呢?請聽下回分解。?

相關文章
相關標籤/搜索