![image.png](https://cdn.nlark.com/yuque/0/2023/png/2548312/1690078539162-4a2c1ab0-6ab8-4c04-b83b-b15517f0df8a.png#averageHue=%23040100&clientId=u8654 ...
本文是《Maven實戰》的讀書筆記,實戰代碼倉庫:https://github.com/goSilver/mvn_in_action
第五章 坐標和依賴
5.1 坐標的定義
Maven定義了這樣一組規則:世界上任何一個構件都可以使用Maven坐標唯一標識,Maven坐標的元素包括groupId、artifactId、version、packaging、classifier。只要我們提供正確的坐標元素,Maven就能找到對應的構件。
<!-- 項目坐標 -->
<!-- groupId定義了項目屬於哪個組,往往是組織名或公司名-->
<groupId>org.chensh</groupId>
<!-- artifactId定義了當前Maven項目在組中唯一的ID -->
<artifactId>chapter_3</artifactId>
<!-- 版本號,分為快照版本號和穩定版本號 -->
<version>1.0-SNAPSHOT</version>
<!-- name元素聲明一個更加友好的項目名稱,非必填 -->
<name>hello-world</name>
groupId:定義當前Maven項目隸屬的實際項目。
artifactId:該元素定義實際項目中的一個Maven項目(模塊)
version:該元素定義Maven項目當前所處的版本
packaging:該元素定義Maven項目的打包方式
classifier:該元素用來幫助定義構建輸出的一些附屬構件
上述5個元素中,groupId、artifactId、version是必須定義的,packaging是可選的(預設為jar),而classifier是不能直接定義的。
5.2 依賴範圍
- classpath
首先需要知道,Maven在編譯項目主代碼的時候需要使用一套classpath。在上例中,編譯項目主代碼的時候需要用到spring-core,該文件以依賴的方式被引入到classpath中。其次,Maven在編譯和執行測試的時候會使用另外一套classpath。上例中的JUnit就是一個很好的例子,該文件也以依賴的方式引入到測試使用的classpath中,不同的是這裡的依賴範圍是test。最後,實際運行Maven項目的時候,又會使用一套classpath,上例中的spring-core需要在該classpath中,而JUnit則不需要。
- 依賴範圍
依賴範圍就是用來控制依賴與這三種classpath(編譯classpath、測試classpath、運行classpath)的關係,Maven有以下幾種依賴範圍:
- compile:編譯依賴範圍。如果沒有指定,就會預設使用該依賴範圍。使用此依賴範圍的Maven依賴,對於編譯、測試、運行三種classpath都有效。典型的例子是spring-core,在編譯、測試和運行的時候都需要使用該依賴。
- test:測試依賴範圍。使用此依賴範圍的Maven依賴,只對於測試classpath有效,在編譯主代碼或者運行項目的使用時將無法使用此類依賴。典型的例子是JUnit,它只有在編譯測試代碼及運行測試的時候才需要。
- provided:已提供依賴範圍。使用此依賴範圍的Maven依賴,對於編譯和測試class-path有效,但在運行時無效。典型的例子是servlet-api,編譯和測試項目的時候需要該依賴,但在運行項目的時候,由於容器已經提供,就不需要Maven重覆地引入一遍。
- runtime:運行時依賴範圍。使用此依賴範圍的Maven依賴,對於測試和運行class-path有效,但在編譯主代碼時無效。典型的例子是JDBC驅動實現,項目主代碼的編譯只需要JDK提供的JDBC介面,只有在執行測試或者運行項目的時候才需要實現上述介面的具體JDBC驅動。
- import(Maven 2.0.9及以上):導入依賴範圍。該依賴範圍不會對三種classpath產生實際的影響,本書將在8.3.3節介紹Maven依賴和dependencyManagement的時候詳細介紹此依賴範圍。
5.3 傳遞性依賴
- 定義
有了傳遞性依賴機制,在使用Spring Framework的時候就不用去考慮它依賴了什麼,也不用擔心引入多餘的依賴。Maven會解析各個直接依賴的POM,將那些必要的間接依賴,以傳遞性依賴的形式引入到當前的項目中。
- 傳遞性依賴和依賴範圍
依賴範圍不僅可以控制依賴與三種classpath的關係,還對傳遞性依賴產生影響。
假設A依賴於B,B依賴於C,我們說A對於B是第一直接依賴,B對於C是第二直接依賴,A對於C是傳遞性依賴。第一直接依賴的範圍和第二直接依賴的範圍決定了傳遞性依賴的範圍,如表5-2所示,最左邊一列表示第一直接依賴範圍,最上面一行表示第二直接依賴範圍,中間的交叉單元格則表示傳遞性依賴範圍。
仔細觀察一下表5-2,可以發現這樣的規律:
- 當第二直接依賴的範圍是compile的時候,傳遞性依賴的範圍與第一直接依賴的範圍一致;當第二直接依賴的範圍是test的時候,依賴不會得以傳遞;
- 當第二直接依賴的範圍是provided的時候,只傳遞第一直接依賴範圍也為provided的依賴,且傳遞性依賴的範圍同樣為provided;
- 當第二直接依賴的範圍是runtime的時候,傳遞性依賴的範圍與第一直接依賴的範圍一致,但compile例外,此時傳遞性依賴的範圍為runtime。
5.4 依賴調解
Maven引入的傳遞性依賴機制,一方面大大簡化和方便了依賴聲明,另一方面,大部分情況下我們只需要關心項目的直接依賴是什麼,而不用考慮這些直接依賴會引入什麼傳遞性依賴。但有時候,當傳遞性依賴造成問題的時候,我們就需要清楚地知道該傳遞性依賴是從哪條依賴路徑引入的。
例如,項目A有這樣的依賴關係:A->B->C->X(1.0)、A->D->X(2.0),X是A的傳遞性依賴,但是兩條依賴路徑上有兩個版本的X,那麼哪個X會被Maven解析使用呢?兩個版本都被解析顯然是不對的,因為那會造成依賴重覆,因此必須選擇一個。Maven依賴調解(Dependency Mediation)的第一原則是:路徑最近者優先。該例中X(1.0)的路徑長度為3,而X(2.0)的路徑長度為2,因此X(2.0)會被解析使用。
依賴調解第一原則不能解決所有問題,比如這樣的依賴關係:A->B->Y(1.0)、A->C->Y(2.0),Y(1.0)和Y(2.0)的依賴路徑長度是一樣的,都為2。那麼到底誰會被解析使用呢?在Maven 2.0.8及之前的版本中,這是不確定的,但是從Maven 2.0.9開始,為了儘可能避免構建的不確定性,Maven定義了依賴調解的第二原則:第一聲明者優先。在依賴路徑長度相等的前提下,在POM中依賴聲明的順序決定了誰會被解析使用,順序最靠前的那個依賴優勝。該例中,如果B的依賴聲明在C之前,那麼Y(1.0)就會被解析使用。
5.5 可選依賴
- 為什麼要使用可選依賴這一特性呢?
可能項目B實現了兩個特性,其中的特性一依賴於X,特性二依賴於Y,而且這兩個特性是互斥的,用戶不可能同時使用兩個特性。比如B是一個持久層隔離工具包,它支持多種資料庫,包括MySQL、PostgreSQL等,在構建這個工具包的時候,需要這兩種資料庫的驅動程式,但在使用這個工具包的時候,只會依賴一種資料庫。
- 原則
關於可選依賴需要說明的一點是,在理想的情況下,是不應該使用可選依賴的。前面我們可以看到,使用可選依賴的原因是某一個項目實現了多個特性,在面向對象設計中,有個單一職責性原則,意指一個類應該只有一項職責,而不是糅合太多的功能。
5.6 排除依賴
傳遞性依賴會給項目隱式地引入很多依賴,這極大地簡化了項目依賴的管理,但是有些時候這種特性也會帶來問題。例如,當前項目有一個第三方依賴,而這個第三方依賴由於某些原因依賴了另外一個類庫的SNAPSHOT版本,那麼這個SNAPSHOT就會成為當前項目的傳遞性依賴,而SNAPSHOT的不穩定性會直接影響到當前的項目。這時就需要排除掉該SNAPSHOT,並且在當前項目中聲明該類庫的某個正式發佈的版本。
代碼中使用exclusions元素聲明排除依賴,exclusions可以包含一個或者多個exclusion子元素,因此可以排除一個或者多個傳遞性依賴。需要註意的是,聲明exclusion的時候只需要groupId和artifactId,而不需要version元素,這是因為只需要groupId和artifactId就能唯一定位依賴圖中的某個依賴。換句話說,Maven解析後的依賴中,不可能出現groupId和artifactId相同,但是version不同的兩個依賴,這一點在5.6節中已做過解釋。該例的依賴解析邏輯如圖5-4所示。
5.7 歸類依賴
<properties>
<springframework.version>2.5.6</springframework.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${springframework.version}</version>
</dependency>
</dependencies>
這裡簡單用到了Maven屬性(14.1節會詳細介紹Maven屬性),首先使用properties元素定義Maven屬性,該例中定義了一個springframework.version子元素,其值為2.5.6。有了這個屬性定義之後,Maven運行的時候會將POM中的所有的${springframework.version}替換成實際值2.5.6。也就是說,可以使用美元符號和大括弧環繞的方式引用Maven屬性。然後,將所有Spring Framework依賴的版本值用這一屬性引用表示。這和在Java中用常量PI替換3.14是同樣的道理,不同的只是語法。
5.8 依賴優化
Maven會自動解析所有項目的直接依賴和傳遞性依賴,並且根據規則正確判斷每個依賴的範圍,對於一些依賴衝突,也能進行調節,以確保任何一個構件只有唯一的版本在依賴中存在。在這些工作之後,最後得到的那些依賴被稱為已解析依賴(Resolved Dependency)。可以運行如下的命令查看當前項目的已解析依賴:
mvn dependency:list
在此基礎上,還能進一步瞭解已解析依賴的信息。將直接在當前項目POM聲明的依賴定義為頂層依賴,而這些頂層依賴的依賴則定義為第二層依賴,以此類推,有第三、第四層依賴。當這些依賴經Maven解析後,就會構成一個依賴樹,通過這棵依賴樹就能很清楚地看到某個依賴是通過哪條傳遞路徑引入的。可以運行如下命令查看當前項目的依賴樹:
mvn dependency:tree
使用dependency:list和dependency:tree可以幫助我們詳細瞭解項目中所有依賴的具體信息,在此基礎上,還有dependency:analyze工具可以幫助分析當前項目的依賴。
mvn dependency:analyze
該結果中重要的是兩個部分。首先是Used undeclared dependencies,意指項目中使用到的,但是沒有顯式聲明的依賴,這裡是spring-context。這種依賴意味著潛在的風險,當前項目直接在使用它們,例如有很多相關的Java import聲明,而這種依賴是通過直接依賴傳遞進來的,當升級直接依賴的時候,相關傳遞性依賴的版本也可能發生變化,這種變化不易察覺,但是有可能導致當前項目出錯。例如由於介面的改變,當前項目中的相關代碼無法編譯。這種隱藏的、潛在的威脅一旦出現,就往往需要耗費大量的時間來查明真相。因此,顯式聲明任何項目中直接用到的依賴。
結果中還有一個重要的部分是Unused declared dependencies,意指項目中未使用的,但顯式聲明的依賴,這裡有spring-core和spring-beans。需要註意的是,對於這樣一類依賴,我們不應該簡單地直接刪除其聲明,而是應該仔細分析。由於dependency:analyze只會分析編譯主代碼和測試代碼需要用到的依賴,一些執行測試和運行時需要的依賴它就發現不了。很顯然,該例中的spring-core和spring-beans是運行Spring Framework項目必要的類庫,因此不應該刪除依賴聲明。當然,有時候確實能通過該信息找到一些沒用的依賴,但一定要小心測試。
第六章 倉庫
6.1 倉庫的定義
在一臺工作站上,可能會有幾十個Maven項目,所有項目都使用maven-compiler-plugin,這些項目中的大部分都用到了log4j,有一小部分用到了Spring Framework,還有另外一小部分用到了Struts2。在每個有需要的項目中都放置一份重覆的log4j或者struts2顯然不是最好的解決方案,這樣做不僅造成了磁碟空間的浪費,而且也難於統一管理,文件的複製等操作也會降低構建的速度。而實際情況是,在不使用Maven的那些項目中,我們往往就能發現命名為lib/的目錄,各個項目lib/目錄下的內容存在大量的重覆。
得益於坐標機制,任何Maven項目使用任何一個構件的方式都是完全相同的。在此基礎上,Maven可以在某個位置統一存儲所有Maven項目共用的構件,這個統一的位置就是倉庫。實際的Maven項目將不再各自存儲其依賴文件,它們只需要聲明這些依賴的坐標,在需要的時候(例如,編譯項目的時候需要將依賴加入到classpath中),Maven會自動根據坐標找到倉庫中的構件,並使用它們。
為了實現重用,項目構建完畢後生成的構件也可以安裝或者部署到倉庫中,供其他項目使用。
6.2 倉庫的佈局
任何一個構件都有其唯一的坐標,根據這個坐標可以定義其在倉庫中的唯一存儲路徑,這便是Maven的倉庫佈局方式。
Maven倉庫是基於簡單文件系統存儲的,我們也理解了其存儲方式,因此,當遇到一些與倉庫相關的問題時,可以很方便地查找相關文件,方便定位問題。例如,當Maven無法獲得項目聲明的依賴時,可以查看該依賴對應的文件在倉庫中是否存在,如果不存在,查看是否有其他版本可用,等等。
6.3 倉庫的分類
對於Maven來說,倉庫只分為兩類:本地倉庫和遠程倉庫。當Maven根據坐標尋找構件的時候,它首先會查看本地倉庫,如果本地倉庫存在此構件,則直接使用;如果本地倉庫不存在此構件,或者需要查看是否有更新的構件版本,Maven就會去遠程倉庫查找,發現需要的構件之後,下載到本地倉庫再使用。如果本地倉庫和遠程倉庫都沒有需要的構件,Maven就會報錯。
在這個最基本分類的基礎上,還有必要介紹一些特殊的遠程倉庫。中央倉庫是Maven核心自帶的遠程倉庫,它包含了絕大部分開源的構件。在預設配置下,當本地倉庫沒有Maven需要的構件的時候,它就會嘗試從中央倉庫下載。
私服是另一種特殊的遠程倉庫,為了節省帶寬和時間,應該在區域網內架設一個私有的倉庫伺服器,用其代理所有外部的遠程倉庫。內部的項目還能部署到私服上供其他項目使用。
- 本地倉庫
一般來說,在Maven項目目錄下,沒有諸如lib/這樣用來存放依賴文件的目錄。當Maven在執行編譯或測試時,如果需要使用依賴文件,它總是基於坐標使用本地倉庫的依賴文件。
一個構件只有在本地倉庫中之後,才能由其他Maven項目使用,那麼構件如何進入到本地倉庫中呢?最常見的是依賴Maven從遠程倉庫下載到本地倉庫中。還有一種常見的情況是,將本地項目的構件安裝到Maven倉庫中。
Install插件的install目標將項目的構建輸出文件安裝到本地倉庫。
- 遠程倉庫
安裝好Maven後,如果不執行任何Maven命令,本地倉庫目錄是不存在的。當用戶輸入第一條Maven命令之後,Maven才會創建本地倉庫,然後根據配置和需要,從遠程倉庫下載構件至本地倉庫。
- 中央倉庫
由於最原始的本地倉庫是空的,Maven必須知道至少一個可用的遠程倉庫,才能在執行Maven命令的時候下載到需要的構件。中央倉庫就是這樣一個預設的遠程倉庫,Maven的安裝文件自帶了中央倉庫的配置。
中央倉庫包含了這個世界上絕大多數流行的開源Java構件,以及源碼、作者信息、SCM、信息、許可證信息等,每個月這裡都會接受全世界Java程式員大概1億次的訪問,它對全世界Java開發者的貢獻由此可見一斑。由於中央倉庫包含了超過2000個開源項目的構件,因此,一般來說,一個簡單Maven項目所需要的依賴構件都能從中央倉庫下載到。這也解釋了為什麼Maven能做到“開箱即用”。
- 私服
私服是一種特殊的遠程倉庫,它是架設在區域網內的倉庫服務,私服代理廣域網上的遠程倉庫,供區域網內的Maven用戶使用。當Maven需要下載構件的時候,它從私服請求,如果私服上不存在該構件,則從外部的遠程倉庫下載,緩存在私服上之後,再為Maven的下載請求提供服務。此外,一些無法從外部倉庫下載到的構件也能從本地上傳到私服上供大家使用,如圖6-2所示。
私服的好處:
- 節省自己的外網帶寬。
- 加速Maven構建。
- 部署第三方構件。
- 提高穩定性,增強控制。
- 降低中央倉庫的負荷。
6.5 快照版本
快照版本的出現是為了提高團隊內部協作時的協作效率。
預設情況下,Maven每天檢查一次更新(由倉庫配置的updatePolicy控制,見第6.4節),用戶也可以使用命令行-U參數強制讓Maven檢查更新,如mvn clean install-U。
當項目經過完善的測試後需要發佈的時候,就應該將快照版本更改為發佈版本。
快照版本只應該在組織內部的項目或模塊間依賴使用,因為這時,組織對於這些快照版本的依賴具有完全的理解及控制權。項目不應該依賴於任何組織外部的快照版本依賴,由於快照版本的不穩定性,這樣的依賴會造成潛在的危險。也就是說,即使項目構建今天是成功的,由於外部的快照版本依賴實際對應的構件隨時可能變化,項目的構建就可能由於這些外部的不受控制的因素而失敗。
第七章 生命周期
7.1 生命周期的定義
Maven的生命周期就是為了對所有的構建過程進行抽象和統一。Maven從大量項目和構建工具中學習和反思,然後總結了一套高度完善的、易擴展的生命周期。這個生命周期包含了項目的清理、初始化、編譯、測試、打包、集成測試、驗證、部署和站點生成等幾乎所有構建步驟。
Maven的生命周期是抽象的,這意味著生命周期本身不做任何實際的工作,在Maven的設計中,實際的任務(如編譯源代碼)都交由插件來完成。每個構建步驟都可以綁定一個或者多個插件行為,而且Maven為大多數構建步驟編寫並綁定了預設插件。
Maven定義的生命周期和插件機制一方面保證了所有Maven項目有一致的構建標準,另一方面又通過預設插件簡化和穩定了實際項目的構建。此外,該機制還提供了足夠的擴展空間,用戶可以通過配置現有插件或者自行編寫插件來自定義構建行為。
7.2 生命周期詳解
Maven擁有三套相互獨立的生命周期,它們分別為clean、default和site。
- clean生命周期的目的是清理項目;
- default生命周期的目的是構建項目;
- site生命周期的目的是建立項目站點。
第八章 聚合與繼承
8.2 聚合
當我們的項目下存在多個模塊時,一個簡單的需求就會自然而然地顯現出來:我們會想要一次構建兩個項目,而不是到兩個模塊的目錄下分別執行mvn命令。Maven聚合(或者稱為多模塊)這一特性就是為該需求服務的。
<groupId>com.mypaas.bigdata</groupId>
<artifactId>bigdata-quality</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>quality-common</module>
<module>quality-dao</module>
<module>quality-stub</module>
<module>quality-service</module>
<module>quality-web</module>
</modules>
這裡的第一個特殊的地方為packaging,其值為POM。回顧account-email和account-persist,它們都沒有聲明packaging,即使用了預設值jar。對於聚合模塊來說,其打包方式packaging的值必須為pom,否則就無法構建。
之後是本書之前都沒提到過的元素modules,這是實現聚合的最核心的配置。用戶可以通過在一個打包方式為pom的Maven項目中聲明任意數量的module元素來實現模塊的聚合。
聚合模塊僅僅是幫助聚合其他模塊構建的工具,它本身並無實質的內容。
Maven會首先解析聚合模塊的POM、分析要構建的模塊、並計算出一個反應堆構建順序(Reactor Build Order),然後根據這個順序依次構建各個模塊。反應堆是所有模塊組成的一個構建結構。
8.3 繼承
面向對象設計中,程式員可以建立一種類的父子結構,然後在父類中聲明一些欄位和方法供子類繼承,這樣就可以做到“一處聲明,多處使用”。類似地,我們需要創建POM的父子結構,然後在父POM中聲明一些配置供子POM繼承,以實現“一處聲明,多處使用”的目的。
Maven提供的dependencyManagement元素既能讓子模塊繼承到父模塊的依賴配置,又能保證子模塊依賴使用的靈活性。在dependencyManagement元素下的依賴聲明不會引入實際的依賴,不過它能夠約束dependencies下的依賴使用。如果子模塊不聲明依賴的使用,即使該依賴已經在父POM的dependencyManagement中聲明瞭,也不會產生任何實際的效果。
當項目中的多個模塊有同樣的插件配置時,應當將配置移到父POM的pluginManagement元素中。
8.4 聚合與繼承的關係
多模塊Maven項目中的聚合與繼承其實是兩個概念,其目的完全是不同的。前者主要是為了方便快速構建項目,後者主要是為了消除重覆配置。
對於聚合模塊來說,它知道有哪些被聚合的模塊,但那些被聚合的模塊不知道這個聚合模塊的存在。對於繼承關係的父POM來說,它不知道有哪些子模塊繼承於它,但那些子模塊都必須知道自己的父POM是什麼。如果非要說這兩個特性的共同點,那麼可以看到,聚合POM與繼承關係中的父POM的packaging都必須是pom,同時,聚合模塊與繼承關係中的父模塊除了POM之外都沒有實際的內容。
8.5 約定由於配置
8.6 反應堆
在一個多模塊的Maven項目中,反應堆(Reactor)是指所有模塊組成的一個構建結構。對於單模塊的項目,反應堆就是該模塊本身,但對於多模塊項目來說,反應堆就包含了各模塊之間繼承與依賴的關係,從而能夠自動計算出合理的模塊構建順序。
- 反應堆的構建順序
Maven按序讀取POM,如果該POM沒有依賴模塊,那麼就構建該模塊,否則就先構建其依賴模塊,如果該依賴還依賴於其他模塊,則進一步先構建依賴的依賴。
模塊間的依賴關係會將反應堆構成一個有向非迴圈圖(Directed Acyclic Graph,DAG),各個模塊是該圖的節點,依賴關係構成了有向邊。這個圖不允許出現迴圈,因此,當出現模塊A依賴於B,而B又依賴於A的情況時,Maven就會報錯。
- 反應堆裁剪
一般來說,用戶會選擇構建整個項目或者選擇構建單個模塊,但有些時候,用戶會想要僅僅構建完整反應堆中的某些個模塊。換句話說,用戶需要實時地裁剪反應堆。
Maven提供很多的命令行選項支持裁剪反應堆,輸入mvn-h可以看到這些選項:
- -am,also-make同時構建所列模塊的依賴模塊
- -amd,also-make-dependents同時構建依賴於所列模塊的模塊
- -pl,projects<arg>構建指定的模塊,模塊間用逗號分隔
- -rf,resume-from<arg>從指定的模塊回覆反應堆
第十章 使用Maven進行測試
10.1 maven-surefire-plugin簡介
Maven本身並不是一個單元測試框架,Java世界中主流的單元測試框架為JUnit(http://www.junit.org/)和TestNG(http://testng.org/)。Maven所做的只是在構建執行到特定生命周期階段的時候,通過插件來執行JUnit或者TestNG的測試用例。這一插件就是maven-surefire-plugin,可以稱之為測試運行器(Test Runner),它能很好地相容JUnit 3、JUnit 4以及TestNG。
在預設情況下,maven-surefire-plugin的test目標會自動執行測試源碼路徑(預設為src/test/java/)下所有符合一組命名模式的測試類。這組模式為:
**/Test.java:任何子目錄下所有命名以Test開頭的Java類。
**/Test.java:任何子目錄下所有命名以Test結尾的Java類。
**/*TestCase.java:任何子目錄下所有命名以TestCase結尾的Java類。
10.2 跳過測試
不管怎樣,我們總會要求Maven跳過測試,這很簡單,在命令行加入參數skipTests就可以了。例如:
$mvn package-DskipTests
當然,也可以在POM中配置maven-surefire-plugin插件來提供該屬性,如代碼清單10-12所示。但這是不推薦的做法,如果配置POM讓項目長時間地跳過測試,則還要測試代碼做什麼呢?
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
有時候用戶不僅僅想跳過測試運行,還想臨時性地跳過測試代碼的編譯,Maven也允許你這麼做,但記住這是不推薦的:
$mvn package-Dmaven.test.skip=true
參數maven.test.skip同時控制了maven-compiler-plugin和maven-surefire-plugin兩個插件的行為,測試代碼編譯跳過了,測試運行也跳過了。
10.3 動態指定要運行的測試用例
maven-surefire-plugin提供了一個test參數讓Maven用戶能夠在命令行指定要運行的測試用例。
- 指定單個要運行的測試類
$mvn test-Dtest=RandomGeneratorTest
- 使用星號指定運行匹配的測試類名
$mvn test-Dtest=Random*Test
- 使用逗號指定運行多個測試類
$mvn test-Dtest=RandomGeneratorTest,AccountCaptchaServiceTest
- 逗號和星號組合使用
$mvn test-Dtest=Random*Test,AccountCaptchaServiceTest
使用test參數用戶可以從命令行靈活地指定要運行的測試類。可惜的是,maven-surefire-plugin並沒有提供任何參數支持用戶從命令行跳過指定的測試類,好在用戶可以通過在POM中配置maven-surefire-plugin排除特定的測試類。
10.4 包含與排除測試用例
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<includes>
<include>**/*Tests.java</include>
</includes>
</configuration>
</plugin>
使用了/*Tests.java來匹配所有以Tests結尾的Java類,兩個星號用來匹配任意路徑,一個星號*匹配除路徑風格符外的0個或者多個字元。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<excludes>
<exclude>**/*UserTest.java</exclude>
</excludes>
</configuration>
</plugin>
以上配置排除了以UserTest結尾的測試類。
10.5 測試報告
- 基本的測試報告
預設情況下,maven-surefire-plugin會在項目的target/surefire-reports目錄下生成兩種格式的錯誤報告:
- 簡單文本格式
- 與JUnit相容的XML格式
- 測試覆蓋率報告
測試覆蓋率是衡量項目代碼質量的一個重要的參考指標。Cobertura是一個優秀的開源測試覆蓋率統計工具(詳見http://cobertura.sourceforge.net/),Maven通過cobertura-maven-plugin與之集成,用戶可以使用簡單的命令為Maven項目生成測試覆蓋率報告。
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<version>2.7</version>
<configuration>
<formats>
<format>html</format>
</formats>
<check/>
</configuration>
</plugin>
10.6 運行TestNG測試
TestNG是Java社區中除JUnit之外另一個流行的單元測試框架。NG是Next Generation的縮寫,譯為“下一代”。
TestNG較JUnit的一大優勢在於它支持測試組的概念,如下的註解會將測試方法加入到兩個測試組util和medium中:
@Test(groups={"util","medium"})
由於用戶可以自由地標註方法所屬的測試組,因此這種機制能讓用戶在方法級別對測試進行歸類。這一點JUnit無法做到,它只能實現類級別的測試歸類。
第十三章 版本管理
閱讀本章的時候還需要分清版本管理(Version Management)和版本控制(Version Control)的區別。版本管理是指項目整體版本的演變過程管理,如從1.0-SNAPSHOT到1.0,再到1.1-SNAPSHOT。版本控制是指藉助版本控制工具(如Subversion)追蹤代碼的每一個變更。本章重點講述的是版本管理,但是讀者將會看到,版本管理通常也會涉及一些版本控制系統的操作及概念。請在閱讀的時候特別留意這兩者的關係和區別。