擁抱變化——持續整合(CI)實踐心得

cnbird發表於2013-03-07

引子

    記得剛加入趨勢開始開發工作的時候曾被告知,趨勢有一套auto build的系統,會每天夜裡自動把當天check in的程式碼進行構建,生成QA可測試的build。每個RD都得小心提交code,因為專案結束的時候會看auto build的失敗率。可是構建失敗總是在所難免,尤其是每次要提交candidate build給QA做full cycle測試時,總是在最不該發生的時候發生最不可能發生的事情,至今我還記得為了按時出一個合格的candidate build,熬夜等build的經歷。

    Martin Fowler大師在2000的一篇文章中,最早提出了Continuous Integration(CI,持續整合)的概念。此後,CI作為軟體開發的最佳實踐已經被很多的團隊所採納。從今年年初開始,我們開始了一個專案新版本的開發,由於專案具有的不確定性,以及開發週期不是特別緊,我們團隊決定在開發流程和質量控制上嘗試著做些改變,在趨勢,Change是深植入企業文化的一個特質,這裡的Change不單單隻包括老闆和管理團隊自上而下的Change,還包括開發團隊和個體,只要你有想法,你都可以嘗試著去改變。(之前博文中的測試驅動開發的半年實戰心得,也來自我們團隊另一個成員在該專案中對於TDD的新嘗試)

 

簡介

    正如Martin Fowler所說,CI要求開發團隊能夠頻繁地整合開發和測試工作,以便儘早發現問題,減少專案風險。這裡的關鍵是“頻繁”,如果CI也只能每天在夜裡做一次整合,那和原來的daily build又有什麼大的差異呢?

    CI其實是由一系列的最佳實踐所構成:

  • 原始碼的版本控制和管理
  • 自動化構建
  • 自動化測試
  • 程式碼審查
  • 自動發行和部署
  • 持續反饋
  • 等等

    通過持續地對程式碼和測試進行儘早頻繁地整合以儘早發現問題,而為了能夠頻繁地整合,又對自動化提出了很高的要求,畢竟只有機器自動完成才有可能在一天裡整合若干次(如果需要專人來負責這件事情,第一老闆不會同意,成本太高了,其二人總是難免有出錯的時候,結果可能整合本身花費的時間超出了開發的時間,這就得不償失了,其三,由於人的適應性和“包容性”,也會讓規範很難形成並遵守)

 

實踐

    既然CI包含了這麼多實踐,我們決定採用漸進的方式來開展,羅馬不是一日建成的,在這個專案中,我們依次展開了:自動構建、持續反饋、自動測試和自動部署等實踐,其中沒有包含程式碼審查的部分。由於趨勢已經有一套成熟的版本控制和構建系統(採用Perforce進行版本控制),這使得我們的自動構建變得相對容易,所以我們以自動構建為基礎,展開如下的一些實踐:

    – 增量編譯(Incremental Build):CI系統會定期監視Perforce(每5分鐘),一旦發現有新的程式碼check in,就會觸發增量編譯,取出最新check in的程式碼進行編譯,完成後給check in程式碼的人發出郵件通知(成功或者失敗),如果編譯失敗,對應的owner必須優先解決,否則CI會每隔一段時間(5分鐘)再次嘗試,一直到成功為止。由於增量編譯很快,Developer一般在check in程式碼幾分鐘後就能收到通知,如果失敗可以立刻予以修復。

    – 單元測試(Unit Test):CI系統在增量編譯通過的基礎上,會通過我們的測試框架自動呼叫每個模組的單元測試,同樣也會發出測試報告,如果測試失敗也要優先解決。這裡由於我們專案目前還沒有太多單元測試的case,因此我們第一步先把測試框架搭建好,然後對專案中新的程式碼推行單元測試,而對於legacy程式碼,則視情況補充case。

    – 自動發行和部署(Full Build & Deployment):CI系統會定期(3小時)執行Full編譯和打包,最後生成可安裝的發行包,然後通過虛擬機器自動部署並安裝到遠端的虛擬機器上。由於完整的編譯和打包操作很費時間(半小時以上),因此沒有必要對每次check in程式碼都執行。通過自動發行和部署,CI系統可以儘早發現打包和安裝過程中的問題,並儘早予以解決。

    – 元件測試(Component Test):相對於單元測試,元件測試更關注於各個元件間介面部分的測試,此外,單元測試一般要求沒有外部環境的依賴,比如資料庫,DNS等,如果必須有這些依賴必須通過mock的方式解決,而元件測試則執行在真實的產品環境裡,因此資料庫、DNS等外部依賴都可以作為測試的輸入輸出。這部分和單元測試一樣,由於沒有現有的基礎,因此我們優先搭建好測試框架,然後對新程式碼進行測試,對於legacy程式碼則視情況補充。

    – 自動化功能測試(Feature Test):這是我們現有做的做多一部分,趨勢QA會對很多的功能測試case進行自動化,自然在CI系統中也會把這部分納入。我們採用每天一次的週期來執行這部分測試。

    – 覆蓋率(Coverage):既然在CI裡包含了這麼多的測試的內容,那如何衡量測試的效果和標準?覆蓋率便是一個可行的量化標準。因此我們在CI中也加入了覆蓋率的統計,通過程式碼覆蓋工具,我們可以得到測試執行的程式碼行覆蓋率和條件覆蓋率。這些結果也會被彙總到CI報表裡,這樣我們隨時看到測試的效果和歷史資料的改進。

    – 反饋及視覺化:在CI裡還有一個很重要的部分就是反饋和視覺化。有效的反饋機制可以讓產品的問題儘早暴露並通知到合適的人。而同時通過CI中的視覺化可以展示很多資訊,比如測試用例的數量,覆蓋率,成功和失敗構建的次數,程式碼check in次數等等。通過視覺化,可以達到:1、讓團隊成員瞭解專案現狀,增加成就感(想想如果你每天可以隨時在報表裡看到由於自己的工作而增加的部分,是不是很酷?)2、讓manager隨時瞭解團隊現狀,減少專案風險。

 

工具

    工欲善其事,必先利其器。這裡介紹一下我們在CI實踐中用到的一些工具以及選擇的理由。

    CruiseControl,我們選擇它開始CI系統,在選擇它之前我們也考察了其他一些CI系統,最終CC勝出也是有很多理由的:

    – 首先很重要的理由CC是免費開源的系統,我們在CI上也剛剛開始嘗試,不太可能去考慮那些很貴的商業系統

    – 其次,CC提供了很多的SCM工具的整合,比如Perforce,SVN,Starteam等等

    – 此外,CC有非常好的擴充套件性和定製能力,在CC的build loop裡可以新增不同的Task來增加額外的支援,比如我們的很多測試task、覆蓋率等都可以很方便地加入。

    – 最後,CC有著很多的使用者,因此在網上可以找到比較多的支援文件。

    Python,我們選擇python作為我們很多定製task的編制指令碼,同時也使用python作為測試框架的指令碼(以python測試框架Nose為基礎)

    CppUnit/xUnit,這個不用介紹,單元測試必選的測試框架。

    Bullseye,使用bullseye作為覆蓋率工具,它可以統計行覆蓋率和條件覆蓋率,並且有很好的命令列指令碼支援。

    VMWare,元件測試和功能測試都需要自動部署到虛擬機器執行,因此我們選擇了簡單好用的VIPerl作為VM的控制指令碼。用它進行VM snapshot管理任務。

    此外,還用到了很多附屬的工具集,比如ssh,expect,ant,XML/XSLT,等等。在構建CI系統中一個體會就是,儘可能使用現有的工具,通過粘合膠水(比如python)將它們串起來完成複雜的任務,從頭造輪子很少是CI最有效率的選擇。

 

心得

    最後,談談在這次CI實踐中感受到的一些心得:

    CI和process及agile:敏捷程式設計中要求在每個小迭代中都有交付件,因此要求每個迭代都有完整的整合及測試工作,因而CI是一個很好的敏捷實踐,用以保證交付件的質量。如果沒有很好的CI,很難做到真正的敏捷。此外,CI的引入也會對現有流程形成一定的影響,一個實際的例子就是:以前RD總是在每天晚上下班前把當天完成的程式碼check in,而現在則是,完成了一部分就立即check in一部分,並等待幾分鐘,確保check in的程式碼不會讓CI失敗。

    CI和testing及automation:其實在前面的實踐中也已經看到,CI中包含了很多的測試實踐,比如單元測試、元件測試、功能測試、系統測試等等。Integration不只是Compile,更多地是通過測試來保證質量。這對RD和QA都提出了更高的要求,首先,持續意味著我們必須要保證測試的一直可用,在實施CI之前,我們也有單元測試,但單元測試往往在進入alpha或beta後就再也沒人關心和維護了,在專案結束時甚至單元測試的程式連編譯都不能通過。其次,自動化的要求意味著必須要更好地去考慮產品設計、實現、以及測試的設計工作,一個低耦合的架構才有可能更多地自動化,糟糕的設計工作會讓自動化根本無法進行。

    CI和cross-platform開發:趨勢很多專案都有多個平臺的版本,因此對軟體的跨平臺開發也有很高的要求。那CI對跨平臺有什麼意義呢?如果我們在多個開發平臺上都有響應的CI系統,那我們在開發任何一個平臺的時候,新增或者修改的程式碼都可以及時通過其他平臺上的CI系統得到儘早的驗證和反饋。這樣,通過CI可以更好地要求開發人員考慮跨平臺的需要,不能因為一個平臺的程式碼而讓其他平臺的CI失敗。

    “持續”:我想對CI裡“持續”的理解可以從兩方面來談,首先是持續地整合產品,儘早地發現問題;其次,也可以把這裡的持續理解為持續改進,正如前面說的,CI裡包括很多的實踐,我們不可能一下子引入全部,這就要求我們有持續改進的sense,持續地引入新的實踐(比如加入程式碼審查等)、持續地加入新的case、持續地完善CI和process,在改進的同時,CI又很好地保證了已有部分的長期有效,不過像猴子摘西瓜那樣,缺少歷史的積澱。

    企業文化和公司的支援:最後一點心得,和CI關係不大,但在任何公司、任何組織中,要想能不斷改進、嘗試新的實踐和流程,必然離不開組織和制度的支援。我們在實踐CI過程中,manager們給了團隊很多的自由,可以充分去發掘,同時允許失敗,這是任何一個實踐能夠有所收穫的必備前提。

    最後,推薦一本關於持續整合的書籍:Continuous Integration: Improving Software Quality and Reducing Risk(持續整合:軟體質量改進和風險降低之道),它對CI進行比較全面的介紹,可以從這本書裡開始對CI做個全面的瞭解。

    CI並不是軟體開發的銀彈,它也並不嘗試解決軟體開發中固有的很多問題,但通過採用CI,可以更好地控制和降低風險,並能更好地保證團隊和流程走在不斷成功和改進的正確道路上,從而讓我們有更大的信心去release產品, refractor程式碼,agile流程。

    擁抱敏捷、擁抱變化、擁抱CI!


相關文章