【DevCloud · 敏捷智庫】暴走在發佈前夜的開發,你怕不怕?

摘要:每月都有2天開發團隊要通宵熬夜,你們苦不堪言。有個別的開發同窗,罵完公司罵同事,罵完同事罵客戶的,甚至連本身都不放過……

來自一個CEO的敘述

在一次企業交流會上,一個公司的CEO提道,「咱們公司作敏捷開發的轉型有一段時間了,採用的是4週一迭代,相比以前的瀑布式開發,咱們能夠在每個月就讓客戶看到咱們的成果物,這確實爲公司和客戶搭建起了良好的溝通橋樑。可是,也出現了一個很差的狀況,就是開發和客戶以前的矛盾激化了,因爲採用了迭代,因此每月都有2天開發團隊要通宵熬夜,你們苦不堪言。有個別的開發同窗,罵完公司罵同事,罵完同事罵客戶的,甚至連本身都不放過……」web

那些暴走在發佈前夜的開發,你是否也碰見過呢?面試

感同身受的無奈和憤怒

來自開發的一波怒氣數據庫

「我暈,這誰提的代碼啊,上來就白頁面了,還玩個球啊!」編程

「我倒,我本地都是好用的啊,怎麼部署到環境上不行了呢!」segmentfault

「我去,這哪一個大兄弟提了這麼些代碼,太能寫了。我和他代碼衝突多到想哭!」api

「個人代碼讓哪一個孫子給覆蓋了,我一下午白寫了,別讓我查出來誰幹的哈!」安全

「催催催,催個大腦殼啊,你行你來,不行就別說話,合代碼這事是人乾的活嗎!」服務器

「幾位大哥,我知道問題出在哪了,我這還有段代碼沒提交上去呢,嘿嘿,很差意思哈。」「我*!」網絡

來自領導的心酸無奈框架

領導:「每次發佈前,我都不太想去大家開發那,太壓抑了!,你說咋辦?」

骨幹:「咋辦,還能咋辦,換人唄。一個個都不懂咋開發還能咋辦。」

領導:「不是都通過筆試面試進來的嗎?咋還能不懂開發呢?」

骨幹:「開發不是開發完了就行,得好用啊。這幫小年輕就知道埋頭coding,而後扣出來的都是一堆很差用的代碼,很差用倒也沒啥,直接就往代碼庫裏提交啊!」

領導:「這幫小年輕這麼虎嗎?」

骨幹:「這還不算是虎的呢,還那種代碼衝突了的,無論三七二十一直接就忽略衝突提交,我有好幾次,一拉取最新代碼,一面紅色啊。」

領導:「那你多帶帶他們,告訴他們怎麼作啊!「

骨幹:「我都告訴過不少遍了,每次都說‘我測了啊!’,‘誒?奇怪了!’,‘很差意思哥,我忘提交了’,就這幫小年輕,我是真心帶不動哈。我仍是以爲之前瀑布挺好的,要不就變回去得了。「

領導:「那不行啊,客戶如今挺承認咱們的。用敏捷一個月就能看到新作的東西。效果好,客戶滿意度高。你仍是想一想辦法吧。「

骨幹:「我看仍是換人吧。我是沒法阻止別人不**的「

領導:「……「

問題出在哪裏

不知道讀者讀到這裏是否感同身受了一下下,若是沒有的話,那麼恭喜你,你很幸運的加入到了一個優秀的團隊或者公司。

不過,可能你的IT生涯是不完整的……

近些年來,整個IT圈子都在宣揚DevOps,也所以不少人都知道Dev的工做是給應用系統增長新的功能/修復Bug,而Ops的工做是要保持系統的穩定和高性能,而DevOps後就是要調和了Dev和Ops的矛盾、打破兩者的壁壘,以更好的面對變化。你們滿懷着但願開始了敏捷和DevOps,但是每每在新潮和流行的「上層建築」每每發現了「形而下」的落地困難。好比Dev側自身內部的問題。

在上面舉例中的開發的那波怒火和領導的無奈中,誠然有開發人員自身能力素質的問題,但這並不能算是問題,由於全部人都是從菜鳥過來了,沒有人是天生的王者。「怒氣」到底由於什麼?爲何會有怒氣呢?

這個怒氣幾乎都來源於在團隊協做中的「別人」,其實就是在溝通上產生了問題,這能夠歸結爲工做方式方法的問題,說得更直白就是沒有遵循一個良好的軟件開發實踐。

在傳統的瀑布開發的時候,開發每每會在最後「奮力一搏」完成了項目的部署工做,而後就進入了「休息」的狀態,若是有bug就修改bug,若是有其餘的(如文檔補充,希望你沒有這樣的經歷)作其餘的,處於被動觸發這種相對輕鬆的姿態,因此他們從某種程度上說,是能夠接受「別人」的問題的,畢竟忍耐一下就過去了。

而在敏捷的迭代開發中,每次迭代都要產出潛在的可交付成果物,部署和發佈是「永不止境」的,因此就沒有彷彿能看到黎明吹響勝利號角的「奮力一搏」,合代碼的人率先遭遇一波傷害,緊接着其餘的開發一個個承受着傷害(若是出現Bug),而後你們最後再在一塊兒承受某個或某些誰都不知道的問題帶來的打擊……因此每每每次的發佈都是一次心志的磨練和煎熬,尤爲以大工做量週期以月爲單位迭代爲最——這個背後的元兇其實就是「集成」!

有什麼好辦法

雖然相對傳統制造業軟件顯得「年輕」,可是也經歷了無數的大風大浪,這種問題並不專屬於某些公司,這種煩惱和無奈也並不是只有他們才經歷過。早在軟件開發的「上古」時代,軟件界的大神——Martin Fowler就有過這樣的經歷:

「我還能夠生動記起第一次看到大型軟件工程的情景。我當時在一家大型英國電子公司的QA部門實習。個人經理帶我熟悉公司環境,咱們進到一間巨大的,充滿了壓抑感和格子間的的倉庫。我被告知這個項目已經 開發了好幾年,如今正在集成階段,並已經集成了好幾個月。個人嚮導還告訴我沒人知道集成要多久才能結束」。

Martin Fowler不只在他實習的期間認識到集成是一件很耗時並難以預測的過程,而且不少項目和團隊並不把集成當回事。因此爲了解決集成所帶來的問題以及不少人思想上的不覺得意,Martin Fowler 提出了——持續集成。

持續集成 是一種軟件開發實踐。在持續集成中,團隊成員頻繁集成他們的工做成果,通常每人天天至少集成一次,也能夠屢次。每次集成會通過自動構建(包括自動測試)的 檢驗,以儘快發現集成錯誤。

從其定義上來看,持續集成能夠很好的解決開發們的「怒氣」的問題,開發的怒氣根本上來講就是因爲在協做中缺乏「溝通」所形成的,進而將問題推到了「別人」身上,試想若是整個開發的過程當中,每一個團隊的成員彼此作的功能,或者說所提交的代碼是「透明」的,那麼就能夠很大程度上減小這種「別人」的問題了。而且這種「透明」化的週期無需太長時間,最長爲一天,最短於幾個小時內,從而能夠很好的解決了開發團隊集成的問題,下降了交付的風險。

那麼,具體的落地應該有哪些呢?

應該如何落地

欲善其功必先利其器,在談落地實踐以前,首先看看要實現持續集成須要有哪些工具。

基礎工具一:版本控制系統。持續集成最基本的前提條件是對其代碼庫的版本控制,即:對於代碼庫的每一項變動,都必須被安全地存放到專有的版本控制系統中,目前最主流的版本控制系統當屬Git。

基礎工具二:構建工具。構建工具可以經過處理應用的源代碼,自動生成所需的軟件(包)。軟件工具的構建步驟取決於所選用的技術棧。如,Java的應用,可以使用Maven做爲構建工具。

講完了持續集成的定義和基礎工具後,那麼持續集成的過程是怎麼樣的呢?咱們一塊兒看看Martin Fowler是怎麼帶着咱們玩轉持續集成的吧。(如下內容來自Marin Fowler的持續集成)

舉個簡單的例子:如今假設要完成一個軟件的一部分功能,具體任務是什麼並不重要,咱們先假設這個 feature 很小,只用幾個小時就能夠完成。

一開始,將已集成的源代碼複製一份到 本地計算機。這能夠經過從源碼管理系統的 mainline 上 check out 一份源代碼作到。

如今拿到了工做拷貝,接下來須要作一些事情來完成任務。這包括修改產品代碼和添加修改自動化測試。在持續集成中,軟件應該包含完善的可自動運行的測試——自測試代碼。這通常須要用到某一個流行的 XUnit 測試框架。

一旦完成了修改,就會在本身的計算機上啓動一個自動化 build。這會將工做拷貝中的源代碼編譯並連接成爲一個可執行文件,並在之上運行自動化測試。只有當全部的 build 和測試都完成並無任何錯誤時,這個 build 過程才能夠認爲是成功的。

當本地build 成功後,就能夠考慮將改動提交到源碼倉庫。但麻煩的狀況在於別人可能已經在我以前修改過 mainline。這時我須要首先把別人的修改更新到本身的工做拷貝中,再從新作 build。若是別人的代碼和本身的有衝突,就會在編譯或測試的過程當中引發錯誤。本身有責任改正這些問題,並重復這一過程,直到本身的工做拷貝能經過 build 並和 mainline 的代碼同步。

一旦本地的代碼能經過 build,並和 mainline 同步,就能夠把個人修改提交到源碼倉庫。

然而,提交完代碼不表示就完事大吉了。還要作一遍集成 build,此次在集成計算機上並要基於 mainline 的代碼。只有此次 build 成功了,修改纔算告一段落。由於總有可能會忘了什麼東西在本身的機器上而沒有更新到源碼倉庫。只有提交的改動被成功的集成了,此次工做才能算結束。

若是兩個開發者的修改存在衝突,這一般會被第二我的提 交代碼前本地作 build 時發現。即便這時僥倖過關,接下來的集成 build 也會失敗掉。無論怎樣,錯誤都會被很快檢測出來。此時首要的任務就是改正錯誤並讓 build 恢復正常。在持續集成環境裏,必須儘量快地修復每個集成 build。好的團隊應該天天都有多個成功的 build。錯誤的 build 能夠出現,但必須儘快獲得修復。

這樣作的結果是你總能獲得一個穩定的軟件,它可能有一些 bug,但能夠正常工做。每一個人都基於相同的穩定代碼進行開發,並且不會離得太遠,不然就會不得不花很長時間集成回去。Bug被發現得越快,花在改正上的 時間就越短。

上述基本上就是持續集成的過程和步驟了。那麼基於此持續集成又又哪些關鍵的實踐呢?主要有以下幾個:

只維護一個源代碼

在軟件項目裏須要不少文件協調一致才能 build 出產品。跟蹤全部這些文件是一項困難的工做,尤爲是當有不少人一塊兒工做時。因此,一點也不奇怪,軟件開發者們這些年一直在研發這方面的工具。這些工具稱爲 源代碼管理工具,或配置管理,或版本管理系統,或源碼倉庫,或各類其它名字。大部分開發項目中它們是不可分割的一部分。但惋惜的是,並不是全部項目都是如 此。雖然很罕見,但我確實參加過一些項目,它們直接把代碼存到本地驅動器和共享目錄中,亂得一塌糊塗。

因此, 做爲一個最基本的要求,你必須有一個起碼的源代碼管理系統。成本不會是問題,由於有不少優秀的開源工具可用。當前較好的開源工具是 Subversion。(更 老的一樣開源的 CVS 仍被普遍使用,即便是 CVS 也比什麼都不用強得多,但 Subversion 更先進也更強大。)有趣的是,我從與開發者們的交談中瞭解到,不少商業源代碼管理工具其實不比 Subversion 更好。只有一個商業軟件是你們一致贊成值得花錢的,這就是 Perforce。

一旦你有了源代碼管理系統,你要確保全部人都知道到哪裏去取代碼。不該出現這樣的問題:「我應該到哪裏去找xxx文件?」 全部東西都應該存在源碼倉庫裏。

即使對於用了源碼倉庫的團隊,我仍是觀察到一個很廣泛的錯誤,就是他們沒有把 全部東西都放在源碼倉庫裏。通常人們都會把代碼放進去,但還有許多其它文件,包括測試腳本,配置文件,數據庫Schema,安裝腳本,還有第三方的庫,所 有這些build時須要的文件都應該放在源碼倉庫裏。我知道一些項目甚至把編譯器也放到源碼倉庫裏(用來對付早年間那些莫名其妙的C++編譯器頗有效)。 一個基本原則是:你必須可以在一臺乾淨的計算機上重作全部過程,包括checkout和徹底build。只有極少許的軟件須要被預裝在這臺乾淨機器上,通 常是那些又大又穩定,安裝起來很複雜的軟件,好比操做系統,Java開發環境,或數據庫系統。

你必須把 build須要的全部文件都放進源代碼管理系統,此外還要把人們工做須要的其餘東西也放進去。IDE配置文件就很適合放進去,由於你們共享一樣的IDE配 置可讓工做更簡單。

版本控制系統的主要功能之一就是建立 branch 以管理開發流。這是個頗有用的功能,甚至能夠說是一個基礎特性,但它卻常常被濫用。你最好仍是儘可能少用 branch。通常有一個mainline就夠 了,這是一條能反映項目當前開發情況的 branch。大部分狀況下,你們都應該從mainline出發開始本身的工做。(合理的建立 branch 的 理由主要包括給已發佈的產品作維護和臨時性的實驗。)

通常來講,你要把build依賴的全部文件放進代碼管理 系統中,但不要放build的結果。有些人習慣把最終產品也都放進代碼管理系統中,我認爲這是一種壞味道——這意味着可能有一些深層次的問題,極可能是無 法可靠地從新build一個產品。

自動化Build

一般來講,由源代碼轉變成一個可運行的系統是一個複雜的過程,牽扯到編譯,移動文件,將 schema 裝載到數據庫,諸如此類。可是,同軟件開發中的其它相似任務同樣,這也能夠被自動化,也必須被自動化。要人工來鍵入各類奇怪的命令和點擊各類對話框純粹是浪費時間,也容易滋生錯誤。

在大部分開發平臺上都能找到自動化 build 環境的影子。好比 make,這在 Unix 社區已經用了幾十年了,Java 社區也開發出了 Ant,.NET 社區之前用 Nant,如今用 MSBuild。無論你在什麼平臺上,都要確保只用一條命令就能夠運行這些腳本,從而 build 並運行系統。

一 個常見的錯誤是沒有把全部事都放進自動化 build。好比:Build 也應該包括從源碼倉庫中取出數據庫 schema 並在執行環境中設置的過程。我要重申一下前面說過的原則:任何人都應該能從一個乾淨的計算機上 check out 源代碼,而後敲入一條命令,就能夠獲得能在這臺機器上運行的系統。

Build 腳本有不少不一樣的選擇,依它們所屬的平臺和社區而定,但也沒有什麼定勢。儘管大部分的 Java 項目都用 Ant,仍是有一些項目用 Ruby(Ruby Rake 是一個不錯的 build 腳本工具)。咱們也曾經用 Ant 自動化早期的 Microsoft COM 項目,事實證實頗有價值。

一個大型 build 一般會很耗時,若是隻作了很小的修改,你不會想花時間去重複全部的步驟。因此一個好的 build 工具應該會分析哪些步驟能夠跳過。一個通用的辦法是比較源文件和目標文件的修改時間,並只編譯那些較新的源文件。處理依賴關係要麻煩一些:若是一個目標文 件修改了,全部依賴它的部分都要從新生成。編譯器可能會幫你處理這些事情,也可能不會。

根據你的須要,你可能 會想 build 出各類不一樣的東西。你能夠同時 build 系統代碼和測試代碼,也能夠只 build 系統代碼。一些組件能夠被單獨 build。Build 腳本應該容許你在不一樣的狀況中 build 不一樣的 target。

咱們許多人都用 IDE,許多 IDE 都內置包含某種 build 管理功能。然而,相應的配置文件每每是這些 IDE 的專有格式,並且每每不夠健壯,它們離了 IDE 就沒法工做。若是隻是 IDE 用戶本身一我的開發的話,這還可以接受。但在團隊裏,一個工做於服務器上的主 build 環境和從其它腳本里運行的能力更重要。咱們認爲,在 Java 項目裏,開發者能夠用本身的 IDE 作 build,但主 build 必須用 Ant 來作,以保證它能夠在開發服務器上運行。

讓你的Build自行測試

傳統意義上的 build 指編譯,連接,和一些其它能讓程序運行起來的步驟。程序能夠運行並不意味着它也工做正常。現代靜態語言能夠在編譯時檢測出許多 bug,但仍是有更多的漏網之魚。

一種又快又省的查 bug 的方法是在 build 過程當中包含自動測試。固然,測試並不是完美解決方案,但它確實能抓住不少 bug——多到可讓軟件真正可用。極限編程(XP)和測試驅動開發(TDD)的出現很好地普及了自測試代碼的概念,如今已經有不少人意識到了這種技巧的 價值。

常常讀個人著做的讀者都知道我是 TDD 和 XP 的堅決追隨者。可是我想要強調你不須要這二者中任何一個就能享受自測試代碼的好處。二者都要求你先寫測試,再寫代碼以經過測試,在這種工做模式裏測試更多 着重於探索設計而不是發現 bug。這絕對是一個好方法,但對於持續集成而言它並沒必要要,由於這裏對自測試代碼的要求沒有那麼高。(儘管我確定會選擇用 TDD 的方式。)

自測試代碼須要包含一套自動化測試用例,這些測試用例能夠檢查大部分代碼並找出 bug。測試要可以從一條簡單的命令啓動。測試結果必須能指出哪些測試失敗了。對於包含測試的 build,測試失敗必須致使 build 也失敗。

在過去的幾年裏,TDD 的崛起普及了開源的 XUnit 系列工具,這些工具用做以上用途很是理想。對於咱們在 ThoughWorks 工做的人來講,XUnit 工具已經證實了它們的價值。我老是建議人們使用它們。這些最先由 Kent Beck 發明的工具使得設置一個徹底自測試環境的工做變得很是簡單。

毋庸置疑,對於自動測試的工做而言,XUnit 工具只是一個起點。你還必須本身尋找其餘更適合端對端測試的工具。如今有不少此類工具,包括FIT,Selenium,Sahi,Watir,FITnesse, 和許多其它我沒法列在這裏的工具。

固然你不能期望測試發現全部問題。就像人們常常說的:測試經過不能證實沒有 bug。然而,完美並不是是你要經過自測試 build 達到的惟一目標。常常運行不完美的測試要遠遠好過夢想着完美的測試,但實際什麼也不作。

每人天天要向mainline提交代碼

集成的主要工做實際上是溝 通。集成可讓開發者告訴其餘人他們都改了什麼東西。頻繁的溝通可讓人們更快地瞭解變化。

讓開發者提交到 mainline 的一個先決條件是他們必須可以正確地 build 他們的代碼。這固然也包括經過 build 包含的測試。在每一個提交迭代裏,開發者首先更新他們的工做拷貝以與 mainline 一致,解決任何可能的衝突,而後在本身的機器上作 build。在 build 經過後,他們就能夠隨便向 mainline 提交了。

經過頻繁重複上述過程,開發者能夠發現 兩我的之間的代碼衝突。解決問題的關鍵是儘早發現問題。若是開發者每過幾個小時就會提交一次,那衝突也會在出現的幾個小時以內被發現,從這一點來講,由於 尚未作太多事,解決起來也容易。若是讓衝突待上幾個星期,它就會變得很是難解決。

由於你在更新工做拷貝時也 會作 build,這意味着你除了解決源代碼衝突外也會檢查編譯衝突。由於 build 是自測試的,你也能夠查出代碼運行時的衝突。後者若是在一段較長的時間還沒被查出的話會變得尤爲麻煩。由於兩次提交之間只有幾個小時的修改,產生這些問題 只可能在頗有限的幾個地方。此外,由於沒改太多東西,你還能夠用 diff-debugging 的技巧來找 bug。

總的來講,我 的原則是每一個開發者天天都必須提交代碼。實踐中,若是開發者提交的更爲頻繁效果也會更好。你提交的越多,你須要查找衝突錯誤的地方就越少,改起來也越快。

頻繁提交客觀上會鼓勵開發者將工做分解成以小時計的小塊。這能夠幫助跟蹤進度和讓你們感覺到進展。常常會有人一開始根 本沒法找到能夠在幾小時內完成的像樣的工做,但咱們發現輔導和練習能夠幫助他們學習其中的技巧。

每次提交都 應在集成計算機上從新構建 mainline

使用每日提交的策略後,團隊就能獲得不少通過測試的 build。這應該意味着 mainline 應該老是處於一種健康的狀態。但在實踐中,事情並不是老是如此。一個緣由跟紀律有關,人們沒有嚴格遵照在提交以前在本地更新並作 build 的要求。另外一個緣由是開發者的計算機之間環境配置的不一樣。

結論是你必須保證平常的 build 發生在專用的集成計算機上,只有集成 build 成功了,提交的過程纔算結束。本着「誰提交,誰負責」的原則,開發者必須監視 mainline 上的 build 以便失敗時及時修復。一個推論是若是你在下班前提交了代碼,那你在 mainline build 成功以前就不能回家。

我知道主要有兩種方法能夠使用:手動 build,或持續集成服務器軟件。

手動 build 描述起來比較簡單。基本上它跟提交代碼以前在本地所作的那次 build 差很少。開發者登陸到集成計算機,check out 出 mainline 上最新的源碼(已包含最新的提交),並啓動一個集成 build。他要留意 build 的進程,只有 build 成功了他的提交纔算成功。(請查看 Jim Shore 的描述。)

持續集成服務器軟件就像一個監視着源碼倉庫的監視器。每 次源碼倉庫中有新的提交,服務器就會自動 check out 出源代碼並啓動一次 build,而且把 build 的結果通知提交者。這種狀況下,提交者的工做直到收到通知(一般是 email)纔算結束。

在 ThoughtWorks,咱們都是持續集成服務器軟件的堅決支持者,實際上咱們引領了 CruiseControl 和 http://CruiseControl.NET 最 早期的開發,二者都是被普遍使用的開源軟件。此後,咱們還作了商業版的 Cruise 持續集成服務器。咱們幾乎在每個項目裏都會用持續集成服務器,而且對結果很是滿意。

不是每一個人都會用持續集成服務器。Jim Shore 就清楚地表達了爲何他更偏好手動的辦法。我贊成他的見解中的持續集成並不只僅是安裝幾個軟件而已,全部的實踐 都必須爲了能讓持續集成更有效率。但一樣的,許多持續集成執行得很好的團隊也會發現持續集成服務器是個頗有用的工具。

許多組織根據安排好的日程表作例行 build,如天天晚上。這其實跟持續集成是兩碼事,並且作得遠遠不夠。持續集成的最終目標就是要儘量快地發現問題。Nightly build 意味着 bug 被發現以前可能會待上整整一天。一旦 bug 能在系統裏呆這麼久,找到並修復它們也會花較長的時間。

作好持續集成的一個關鍵因素是一旦 mainline 上的 build 失敗了,它必須被立刻修復。而在持續集成環境中工做最大的好處是,你總能在一個穩定的基礎上作開發。mainline 上 build 失敗並不老是壞事,但若是它常常出錯,就意味着人們沒有認真地在提交代碼前先在本地更新代碼和作 build。當 mainline 上 build 真的失敗時,第一時間修復就成了頭等大事。爲了防止在 mainline 上的問題,你也能夠考慮用 pending head 的方法。

當團隊引入持續集成時,這一般是最難搞定的事情之一。在初期,團隊會很是難以接 受頻繁在 mainline 上作 build 的習慣,特別當他們工做在一個已存在的代碼基礎上時更是如此。但最後耐心和堅決不移的實踐經常會起做用,因此不要氣餒。

保持快速 build

持續集成的重點就是快速反饋。沒有什麼比緩慢的 build 更能危害持續集成活動。這裏我必須認可一個奇思怪想的老傢伙關於 build 快慢標準的的玩笑(譯者注:原文如此,不知做者所指)。個人大部分同事認爲超過1小時的 build 是不能忍受的。團隊們都夢想着把 build 搞得飛快,但有時咱們也確實會發現很難讓它達到理想的速度。

對大多數項目來講,XP 的10分鐘 build 的指導方針很是合理。咱們如今作的大多數項目都能達到這個要求。這值得花些力氣去作,由於你在這裏省下的每一分鐘都能體如今每一個開發者每次提交的時候。持 續集成要求頻繁提交,因此這積累下來能節省不少時間。若是你一開始就要花1小時的時間作 build,想加快這個過程會至關有挑戰。即便在一個從頭開始的新項目裏,想讓 build 始終保持快速也是頗有挑戰的。至少在企業應用裏,咱們發現常見的瓶頸出如今測試時,尤爲當測試涉及到外部服務如數據庫。

也許最關鍵的一步是開始使用分階段build(staged build)。分階段 build(也被稱做 build 生產線)的基本想法是多個 build 按必定順序執行。向 mainline 提交代碼會引起第一個 build,我稱之爲提交 build(commit build)。提交 build 是當有人向 mainline 提交時引起的 build。提交 build 要足夠快,所以它會跳過一些步驟,檢測 bug 的能力也較弱。提交 build 是爲了平衡質量檢測和速度,所以一個好的提交 build 至少也要足夠穩定以供他人基於此工做。

一旦提交 build 成功,其餘人就能夠放心地基於這些代碼工做了。但別忘了你還有更多更慢的測試要作,能夠另找一臺計算機來運行運行這些測試。

一個簡單的例子是兩階段 build。第一階段會編譯和運行一些本地測試,與數據庫相關的單元測試會被徹底隔離掉(stub out)。這些測試能夠運行得很是快,符合咱們的10分鐘指導方針。可是全部跟大規模交互,尤爲是真正的數據庫交互的 bug 都沒法被發現。第二階段的 build 運行一組不一樣的測試,這些測試會調用真正的數據庫並涉及更多的端到端的行爲。這些測試會跑上好幾小時。

這種狀況下,人們用第一階段做爲提交 build,並把這做爲主要的持續集成工做。第二階段 build 是次級build,只有 在須要的時候才運行,從最後一次成功的提交 build 中取出可執行文件做進一步測試。若是次級 build 失敗了,你們不會馬上停下手中全部工做去修復,但團隊也要在保證提交 build 正常運行的同時儘快修正 bug。實際上次級 build 並不是必定要正常運行,只要 bug 都可以被檢查出來而且能儘快獲得解決就好。在兩階段 build 的例子裏,次級 build 常常只是純粹的測試,由於一般只是測試拖慢了速度。

若是次級 build 檢查到了 bug,這是一個信號,意味着提交 build 須要添加一個新測試了。你應該儘量把次級 build 失敗過的測試用例都添加到提交 build 中,使得提交 build 有能力驗證這些 bug。每當有 bug 繞過提交測試,提交測試總能經過這種方法被增強。有時候確實沒法找到測試速度和 bug 驗證兼顧的方法,你不得不決定把這個測試放回到次級 build 裏。但大部分狀況下都應該能夠找到合適加入提交 build 的測試。

上面這個例子是關於兩階段 build,但基本原則能夠被推廣到任意數量的後階段 build。提交 build 以後的其它 build 均可以同時進行,因此若是你的次級測試要兩小時才能完成,你能夠經過用兩臺機器各運行一半測試來快一點拿到結果。經過這個並行次級 build 技巧,你能夠向平常 build 流程中引入包括性能測試在內的各類自動化測試。(當我過去幾年內參加 Thoughtworks 的各類項目時,我碰到了不少有趣的技巧,我但願可以說服一些開發者把這些經驗寫出來。)

在模擬生產環境中進行測試

測試的關鍵在於在受控條件下找出系統內可能在實際生產中出現的任何問題。這裏一個明顯的因素是生產系 統的運行環境。若是你不在生產環境作測試,全部環境差別都是風險,可能最終形成測試環境中運行正常的軟件在生產環境中沒法正常運行。

天然你會想到創建一個與生產環境儘量徹底相同的測試環境。用相同的數據庫軟件,還要同一個版本;用相同版本的操做系統;把全部生產環 境用到的庫文件都放進測試環境中,即便你的系統沒有真正用到它們;使用相同的IP地址和端口;以及相同的硬件;

好吧,現實中仍是有不少限制的。若是你在寫一個桌面應用軟件,想要模擬全部型號的裝有不一樣第三方軟件的臺式機來測試顯然是不現實的。相似的,有些生產環境可 能由於過於昂貴而沒法複製(儘管我常碰到出於經濟考慮拒絕複製不算太貴的環境,結果得不償失的例子)。即便有這些限制,你的目標仍然是儘量地複製生產環 境,而且要理解並接受因測試環境和生產環境不一樣帶來的風險。

若是你的安裝步驟足夠簡單,無需太多交互,你也許能在一個模擬生產環境裏運行提交 build。但事實上系統常常反應緩慢或不夠穩定,這能夠用 test double 來解決。結果經常是提交測試爲了速度緣由在一個假環境內運行,而次級測試運行在模擬真實的生產環境中。

我注意到愈來愈多人用虛擬化來搭建測試環境。虛擬機的狀態能夠被保存,所以安裝並測試最新版本的build相對簡單。此外,這可讓你在一臺機器上運行多個測試,或在一臺機器上模擬網絡裏的多臺主機。隨着虛擬化性能的提高,這種選擇看起來愈來愈可行。

讓每一個人都能輕易得到最新的可執行文件

軟件開發中最困難的部分是肯定你的軟件行爲符合預期。咱們發現事先清楚並正確描述需求很是困難。對人們而言,在一個有缺陷的東西上指出須要修改的地方要容易得多。敏捷開發過程承認這種行爲,並從中受益。

爲了以這種方式工做,項目中的每一個人都應該能拿到最新的可執行文件並運行。目的能夠爲了 demo,也能夠爲了探索性測試,或者只是爲了看看這周有什麼進展。

這作起來其實至關簡單:只要找到一個你們都知道的地方來放置可執行文件便可。能夠同時保存多份可執行文件以備使用。每次放進去的可執行文件應該要經過提交測試,提交測試越健壯,可執行文件就會越穩定。

若是你採用的過程是一個足夠好的迭代過程,把每次迭代中最後一個 build 放進去一般是明智的決定。Demo 是一個特例,被 demo 的軟件特性都應該是演示者熟悉的特性。爲了 demo 的效果值得犧牲掉最新的 build,轉而找一個早一點但演示者更熟悉的版本。

每一個人都能看到進度

持續集成中最重要的是溝通。你須要保證每一個人都能輕易看到系統的狀態和最新的修改。

溝通的最重要的途徑之一是 mainline build。若是你用 Cruise,一個內建的網站會告訴你是否正有 build 在進行,和最近一次 mainline build 的狀態。許多團隊喜歡把一個持續工做的狀態顯示設備鏈接到 build 系統來讓這個過程更加引人注目,最受歡迎的顯示設備是燈光,綠燈閃亮表示 build 成功,紅燈表示失敗。一種常見的選擇是紅色和綠色的熔岩燈,這不只僅指示 build 的狀態,還能指示它停留在這個狀態的時間長短,紅燈裏出現氣泡表示 build 出問題已經太長時間了。每個團隊都會選擇他們本身的 build 傳感器。若是你的選擇帶點幽默性和娛樂性效果會更好(最近我看到有人在實驗跳舞兔)。

即便你在使用手動持續集成,可見程度依然很重要。Build 計算機的顯示器能夠用來顯示 mainline build 的狀態。你極可能須要一個 build 令牌放在正在作 build 那人的桌子上(橡皮雞這種看上去傻傻的東西最好,緣由同上)。有時人們會想在 build 成功時弄出一點噪音來,好比搖鈴的聲音。

持續集成服務器軟件的網頁能夠承載更多信息。Cruise 不只顯示誰在作 build,還能指出他們都改了什麼。Cruise 還提供了一個歷史修改記錄,以便團隊成員可以對最近項目裏的狀況有所瞭解。我知道 team leader喜歡用這個功能瞭解你們手頭的工做和追蹤系統的更改。

使用網站的另外一大優勢是便於那些遠程工做的人瞭解項目的狀態。通常來講,我傾向於讓項目中發揮做用的成員都坐在一塊兒工做,但一般也會有一些外圍人員想要了解項目的動態。若是組織想要把多個項目的 build狀況聚合起來以提供自動更新的簡單狀態時,這也會頗有用。

好的信息展現方式不只僅依賴於電腦顯示器。我最喜歡的方式出現於一箇中途轉入持續集成的項目。很長時間它都沒法拿出一個穩定的 build。咱們在牆上貼了一全年的日曆,每一天都是一個小方塊。每一天若是 QA 團隊收到了一個能經過提交測試的穩定 build,他們都會貼一張綠色的貼紙,不然就是紅色的貼紙。日積月累,從日曆上能看出 build 過程在穩定地進步。直到綠色的小方塊已經佔據了大部分的空間時,日曆被撤掉了,由於它的使命已經完成了。

自動化部署

自動化集成須要多個環境,一個運行提交測試,一個或多個運行次級測試。天天在這些環境之間頻繁拷貝 可執行文件可不輕鬆,自動化是一個更好的方案。爲實現自動化,你必須有幾個幫你將應用輕鬆部署到各個環境中的腳本。有了腳本以後,天然而然的結果是你也要 用相似的方式部署到生產環境中。你可能不須要天天都部署到生產環境(儘管我見過這麼作的項目),但自動化可以加快速度並減小錯誤。它的代價也很低,由於它 基本上和你部署到測試環境是一回事。

若是你部署到生產環境,你須要多考慮一件事情:自動化回滾。壞事情隨時可 能發生,若是狀況不妙,最好的辦法是儘快回到上一個已知的正常狀態。可以自動回滾也會減輕部署的壓力,從而鼓勵人們更頻繁地部署,使得新功能更快發佈給用 戶。(Ruby on Rails 社區開發了一個名爲 Capistrano 的工具,是這類工具很好的表明。)

我還在服 務器集羣環境中見過滾動部署的方法,新軟件每次被部署到一個節點上,在幾小時時間內逐步替換掉原有的軟件。

在 web 應用開發中,我碰到的一個有趣的想法是把一個試驗性的 build 部署到用戶的一個子集。團隊能夠觀察這個試驗 build 被使用的狀況,以決定是否將它部署到全體用戶。你能夠在作出最終決定以前試驗新的功能和新的 UI。自動化部署加上良好的持續集成的紀律是這項工做的基礎。

(以上內容來自Marin Fowler的持續集成)

寫在最後

「個人腦海中仍是會浮現出第一段描述的早期軟件項目。他們已經到了一個漫長項 目的末期(至少他們指望如此),但仍是不知道距離真正的結束有多遠。」這是來自Martin Fowler曾經歷過的感覺。

而文中的第二段的那些開發們的「怒氣」是筆者從十年前作開發的時候,所經歷過的幾個團隊所發生過的。

在集成的過程當中,總會有種種沒法預測的事情發生,不管是人仍是事,你根本沒法預測其進展從而很容易進入到迷茫地帶,每個處在迷茫地帶的人都很難去作到輕鬆應對,長此以往開發人員會產生疲於奔命之感,致使團隊沒法凝聚成「拳頭」打出強有力的一拳。基於這種狀況,筆者認爲這正是咱們引入持續集成的緣由所在。

筆者認爲Martin Fowler關於持續集成的落地實踐部分已經比較詳盡徹底能夠用來你們公共參考學習,因此沒有在關公面前耍大刀,故引用於此文章。可是在咱們持續集成實際的過程當中必定會遇到不少問題,好比提高build效率的分段build策略或者其餘實際的需求等,這都須要咱們在平常工做中,經過迭代不斷的來研究和完善其實踐的方法,並在回顧的過程當中加以討論、分析總結,最終提高團隊的研發效率。也能夠使用一些大廠如華爲雲DevCloud做爲持續集成的工具,其提供專業的一站式解決方案,方便了中小企業的DevOps落地。

點擊關注,第一時間瞭解華爲雲新鮮技術~