Maven實戰(四)——基於Maven的持續集成實踐

Martin的《持續集成》

相信很多讀者和我一樣,最早接觸到持續集成的概念是來自Martin的著名文章《持續集成》,該文最早發佈於2000年9月,之後在2006年進行了一次修訂,它清晰地解釋了持續集成的概念,並總結了10條實踐,它們分別爲:

  • 只維護一個源碼倉庫

  • 自動化構建

  • 讓構建自行測試

  • 每人每天向主幹提交代碼

  • 每次提交都應在持續集成機器上構建主幹

  • 保持快速的構建

  • 在模擬生產環境中測試

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

  • 每個人都能看到進度

  • 自動化部署

原始文章距今已10年有餘,這在軟件行業中算是很長的時間了,但我們都能看到Martin總結的這些實踐依舊閃耀着光芒,依舊有很多團隊在努力實踐它們並得到了豐厚的回報,當然也有很多團隊因爲各種原因拒絕實踐持續集成從而無法體會到箇中好處。

從這10條實踐中我們能找到很多流行開源工具的影子,例如版本控制工具cvs、svn、git,自動化構建工具Maven、Ant,自動化測試框架JUnit、TestNG,以及持續集成服務器CruiseControl和Hudson等等。其實不論你是否實踐持續集成,單獨使用這其中的很多工具都能發揮極大的價值,持續集成的一大意義在於它引入了一個有效的流程,能讓這些工具有機融合,並相互促進。 關於持續集成還有一本獲得Jolt大獎的圖書,名爲《持續集成——軟件質量改進和風險降低之道》。但無論是Martin的文章,還是這本圖書,都沒有闡述使用Maven作爲自動化構建工具實施持續集成的細節。本文旨在介紹一些基於Maven實施持續集成的實踐,希望這些經驗能從具體處幫助到讀者。

架設私有Maven倉庫

Martin的文章並沒有涉及到依賴管理的內容,但在Java的世界中,依賴管理是開發人員不得不面對的問題。無論是外部的開源類庫依賴,還是項目內部的模塊間依賴,都需要有效地管理。可以說依賴管理是持續集成核心的內容之一。Maven通過其依賴管理機制和隨處可用的中央倉庫有效地解決了這個問題,用戶只需要在POM中聲明項目所需要的依賴,Maven就能在構建的時候自動從倉庫解析依賴。

不過僅僅這樣是不夠的,我們知道,持續集成的最大好處在於降低風險,簡單地來說就是儘早暴露問題,能讓開發人員及早發現並修復,從而降低修復成本。可是,如果每個人都從中央倉庫重複下載依賴,這是非常耗時的,集成的反饋週期肯定會延長。我已經無數次聽到有人抱怨“Maven在下載整個Internet!”。構建要快!持續集成反饋要快!Maven你不能拖慢這個流程。

幸運的是開源世界有很好的解決方案,只要使用Maven倉庫管理器軟件如Nexus建立一個私有的Maven倉庫,問題就能迎刃而解。原理很簡單,這個位於局域網內的Maven倉庫能夠代理所有外部倉庫,從而避免所有人從Internet重複下載依賴文件。這樣Maven解析依賴的時候僅限於局域網,構建速度就大大地加快了。例如大家都需要使用junit-4.8.2.jar,當第一個人向私有倉庫請求的時候,私有倉庫從中央庫下載並緩存下來,假設耗時10s,之後其他人需要junit-4.8.2.jar的時候,私有倉庫直接使用緩存的文件,這個耗時可能就是1s。如果有100個開發人員使用該文件,那節省的時間就是 100 * 10 - ( 10 + 99 * 1 ) = 891s ,實際情況中依賴的數量可能會是成百上千,那節省的時間就變得非常的可觀。

也許有人會說,我也完全可以將項目依賴加入到版本控制中,這一點甚至在《卓有成效的程序員》中都被明確提及,在該書第5章的"DRY版本控制"一節中,Neal Ford有這麼一段話:“所有用來構建項目的東西都應該被放入版本控制,包括二進制文件(類庫,框架,JAR文件,構建腳本等等)”。作者進一步解釋了其目的,這麼做能夠保證項目不受外部因素影響(如依賴版本變化,甚至丟失),保證構建的穩定,作者也同時提及了一般版本控制工具處理二進制文件的性能問題。拋開這條結論性的實踐,仔細考慮其目的,我們就能發現,私有Maven倉庫同樣能保證構建的穩定,而且能避免版本控制工具處理二進制文件而造成的潛在性能問題。所以,我斗膽說一句,Neal Ford所提的這條實踐OUT了!

私有Maven的倉庫的意義還不僅限於此,結合自動化部署和Maven的SNAPSHOT機制,它能大大促進項目集成的效率。

在模塊化的開發環境中,大家各司其職,專注於自己所負責的模塊,持續集成的規則是,在往版本控制提交代碼前,需要先保證本地構建沒有問題,那一般的做法就是更新所有模塊的代碼並構建。可是,真的需要構建那些其實你並不怎麼關心的模塊麼?且不談一旦構建他人代碼時出錯,你往往會不知所措,這種做法同時也增加了本地構建的時間。

Maven有SNAPSHOT版本的概念,其目的就是讓你能夠構建一個臨時的版本,供團隊他人使用,這樣他們就不必在代碼的層次關心自己的依賴。於是私有Maven倉庫就充當了一箇中介的作用,而持續集成服務器就多了一個職責,每次它成功構建一個模塊,都應該將該模塊的SNAPSHOT版本發佈到Maven倉庫中。現在,大家就不用去構建別人的代碼了,Maven能自動幫你從私有倉庫解析下載依賴的最新SNAPSHOT(使用mvn命令的-U參數強制更新)。注意,除了持續集成服務器外,任何其他人都不應該發佈SNAPSHOT版本到Maven倉庫,因爲只有持續集成服務器的環境是可信任的,你能在本地成功執行mvn clean install並不代表持續集成服務器上該命令能成功,由於每個人的本地環境各有差異,因此集成的成功與否應當以持續集成服務器爲準,而只有集成成功後,SNAPSHOT纔可以被部署到私有倉庫供他人使用。

鑑於上述的原因分析,我認爲在基於Maven的持續集成環境中,再怎麼強調私有Maven倉庫的重要性都是不爲過的。

正確的集成命令

在持續集成服務器上使用怎樣的 mvn 命令集成項目,這個問題乍一看答案很顯然,不就是 mvn clean install 麼?事實上比較好的集成命令會稍微複雜些,下面是一些總結:

  • 不要忘了clean: clean能夠保證上一次構建的輸出不會影響到本次構建。

  • 使用deploy而不是install: 構建的SNAPSHOT輸出應當被自動部署到私有Maven倉庫供他人使用,這一點在前面已經詳細論述。

  • 使用-U參數: 該參數能強制讓Maven檢查所有SNAPSHOT依賴更新,確保集成基於最新的狀態,如果沒有該參數,Maven默認以天爲單位檢查更新,而持續集成的頻率應該比這高很多。

  • 使用-e參數:如果構建出現異常,該參數能讓Maven打印完整的stack trace,以方便分析錯誤原因。

  • 使用-Dmaven.repo.local參數:如果持續集成服務器有很多任務,每個任務都會使用本地倉庫,下載依賴至本地倉庫,爲了避免這種多線程使用本地倉庫可能會引起的衝突,可以使用-Dmaven.repo.local=/home/juven/ci/foo-repo/這樣的參數爲每個任務分配本地倉庫。

  • 使用-B參數:該參數表示讓Maven使用批處理模式構建項目,能夠避免一些需要人工參與交互而造成的掛起狀態。

綜上,持續集成服務器上的集成命令應該爲 mvn clean deploy -B -e -U -Dmaven.repo.local=xxx 。此外,定期清理持續集成服務器的本地Maven倉庫也是個很好的習慣,這樣可以避免浪費磁盤資源,幾乎所有的持續集成服務器軟件都支持本地的腳本任務,你可以寫一行簡單的shell或bat腳本,然後配置以天爲單位自動清理倉庫。需要注意的是,這麼做的前提是你有私有Maven倉庫,否則每次都從Internet下載所有依賴會是一場噩夢。

用好Profile

如果不需要考慮各種不同的環境, 而且你的自動測試(包括集成測試)跑得飛快,那你就不用爲項目建立多個集成任務。但實際的情況是,集成的時候可能要考慮各種環境,例如開發環境、測試環境、產品環境。而當項目越來越大,測試越來越多,控制構建時間在一個可接受的範圍內(例如10分鐘)變得越來越不現實。《持續集成——軟件質量改進和風險降低之道》中介紹了一種名爲分階段構建(staged build)的解決方案,例如你可以將構建分爲兩個部分,第一部分包括了編譯和單元測試等能夠快速結束的任務,第二個部分包括集成測試等耗時較長的任務,只有第一部分成功完成後,才觸發第二部分集成。這麼做的意義在於讓持續集成的反饋儘可能的快。

Maven的Profile機制能夠很好的支持分階段構建。例如,藉助Maven Surefire Plugin,你可以統一單元測試命名爲**UT,統一集成測試命名爲**IT,然後配置Maven Surefire Plugin默認只運行單元測試,然後再編寫一個名爲integrationTest的Profile,在其中配置Maven Surefire Plugin運行集成測試。然後再以此爲基礎分階段構建項目,第一個構建爲 mvn clean install -B -e -U ,第二個構建任務爲 mvn clean deploy -B -e -U -PintegrationTest 。前一個構建成功後再觸發第二個構建,然後才部署至Maven倉庫。值得一提的是,Maven Surefire Plugin能夠很好支持JUnit 3、JUnit 4和TestNG,你可以按照最適合自己的方式來劃分單元測試和集成測試。

另一個常見的分階段構建案例是生成Maven站點,使用 mvn clean site 生成站點往往比較耗時且耗資源,這樣的任務對應的持續集成中的持續審查階段,該階段往往不需要很高的集成頻率。你會希望每10分鐘就檢查源代碼變更並編譯測試,但很少有人會希望每10分鐘讓系統生成一次測試覆蓋率報告、CheckStyle報告等內容,因此合理的做法是使用一個較低的頻率,例如每天,這樣可以避免無謂的資源消耗,更重要的是,這樣不會拖慢本該很快的編譯和單元測試等反饋內容。

還有一些情況是系統需要基於不同環境進行集成,這時候就需要用到Maven的屬性機制、資源過濾、以及前面提到的Profile。篇幅原因,這裏不再展開。

小結

持續集成是敏捷最重要的實踐之一,但如何在基於Maven的環境下實踐持續集成卻鮮有文章詳述,本文介紹了一些該主題的最佳實踐,包括架設私有倉庫、使用正確的集成命令、利用Profile等技術處理分階段構建等等。本文旨在讓廣大Maven用戶認識到這些實踐的存在及重要性,並沒有詳細解釋一些諸如Nexus安裝配置、Maven Surefire Plugin配置、或者說Profile配置使用方面的細節,如果你希望看到更細節的介紹,可以參考我的《Maven實戰》一書。除了上面的內容之外,該書還詳細解釋瞭如何使用Hudson(也許該改稱Jenkins了)這一最流行的開源持續集成服務器。當然,如果你有關於Maven和持續集成方面的經驗,也請不吝分享。


原文地址:http://www.infoq.com/cn/articles/xxb-maven-4-ci

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