maven編寫主代碼與測試代碼
3.2 編寫主代碼?
?
項目主代碼和測試代碼不同,項目的主代碼會被打包到最終的構件中(比如jar),而測試代碼只在運行測試時用到,不會被打包。默認情況下,Maven假設項目主代碼位于src/main/java目錄,我們遵循Maven的約定,創建該目錄,然后在該目錄下創建文件com/juvenxu/mvnbook/helloworld/HelloWorld.java,其內容如代碼清單3-2:
代碼清單3-2:Hello World的主代碼

- <span?style="font-size:?small;">package?com.juvenxu.mvnbook.helloworld;??
- ???
- public?class?HelloWorld??
- {??
- ???public?String?sayHello()??
- ???{??
- ?????return?"Hello?Maven";??
- ???}??
- ??
- ??public?static?void?main(String[]?args)??
- ???{??
- ?????System.out.print(?new?HelloWorld().sayHello()?);??
- ???}??
- }??
- </span>??
???????這是一個簡單的Java類,它有一個sayHello()方法,返回一個String。同時這個類還帶有一個main方法,創建一個HelloWorld實例,調用sayHello()方法,并將結果輸出到控制臺。
關于該Java代碼有兩點需要注意。首先,在95%以上的情況下,我們應該把項目主代碼放到src/main/java/目錄下(遵循Maven的約定),而無須額外的配置,Maven會自動搜尋該目錄找到項目主代碼。其次,該Java類的包名是com.juvenxu.mvnbook.helloworld,這與我們之前在POM中定義的groupId和artifactId相吻合。一般來說,項目中Java類的包都應該基于項目的groupId和artifactId,這樣更加清晰,更加符合邏輯,也方便搜索構件或者Java類。
代碼編寫完畢后,我們使用Maven進行編譯,在項目根目錄下運行命令?mvn clean compile?,我們會得到如下輸出:
?

- <span?style="font-size:?small;">[INFO]?Scanning?for?projects...??
- [INFO]?------------------------------------------------------------------------??
- [INFO]?Building?Maven?Hello?World?Project??
- [INFO]????task-segment:?[clean,?compile]??
- [INFO]?------------------------------------------------------------------------??
- [INFO]?[clean:clean?{execution:?default-clean}]??
- [INFO]?Deleting?directory?D:\code\hello-world\target??
- [INFO]?[resources:resources?{execution:?default-resources}]??
- [INFO]?skip?non?existing?resourceDirectory?D:?\code\hello-world\src\main\resources??
- [INFO]?[compiler:compile?{execution:?default-compile}]??
- [INFO]?Compiling?1?source?file?to?D:?\code\hello-world\target\classes??
- [INFO]?------------------------------------------------------------------------??
- [INFO]?BUILD?SUCCESSFUL??
- [INFO]?------------------------------------------------------------------------??
- [INFO]?Total?time:?1?second??
- [INFO]?Finished?at:?Fri?Oct?09?02:08:09?CST?2009??
- [INFO]?Final?Memory:?9M/16M??
- [INFO]?------------------------------------------------------------------------??
- </span>??
?
clean告訴Maven清理輸出目錄target/,compile告訴Maven編譯項目主代碼,從輸出中我們看到Maven首先執行了clean:clean任務,刪除target/目錄,默認情況下Maven構建的所有輸出都在target/目錄中;接著執行resources:resources任務(未定義項目資源,暫且略過);最后執行compiler:compile任務,將項目主代碼編譯至target/classes目錄(編譯好的類為com/juvenxu/mvnbook/helloworld/HelloWorld.Class)。
上文提到的clean:clean、resources:resources,以及compiler:compile對應了一些Maven插件及插件目標,比如clean:clean是clean插件的clean目標,compiler:compile是compiler插件的compile目標,后文會詳細講述Maven插件及其編寫方法。
至此,Maven在沒有任何額外的配置的情況下就執行了項目的清理和編譯任務,接下來,我們編寫一些單元測試代碼并讓Maven執行自動化測試。
3.3 編寫測試代碼?
?
?
為了使項目結構保持清晰,主代碼與測試代碼應該分別位于獨立的目錄中。3.2節講過Maven項目中默認的主代碼目錄是src/main/java,對應地,Maven項目中默認的測試代碼目錄是src/test/java。因此,在編寫測試用例之前,我們先創建該目錄。
在Java世界中,由Kent Beck和Erich Gamma建立的JUnit是事實上的單元測試標準。要使用JUnit,我們首先需要為Hello World項目添加一個JUnit依賴,修改項目的POM如代碼清單3-3:
代碼清單3-3:為Hello World的POM添加依賴

- <?xml?version="1.0"?encoding="UTF-8"?>??
- <project?xmlns="http://maven.apache.org/POM/4.0.0"??
- ?????????xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"??
- ?????????xsi:schemaLocation="http://maven.apache.org/POM/4.0.0??
- http://maven.apache.org/maven-v4_0_0.xsd">??
- ??<modelVersion>4.0.0</modelVersion>??
- ??<groupId>com.juvenxu.mvnbook</groupId>??
- ??<artifactId>hello-world</artifactId>??
- ??<version>1.0-SNAPSHOT</version>??
- ??<name>Maven?Hello?World?Project</name>??
- ??<dependencies>??
- ????<dependency>??
- ???????<groupId>junit</groupId>??
- ???????<artifactId>junit</artifactId>??
- ???????<version>4.7</version>??
- ???????<scope>test</scope>??
- ????</dependency>??
- ??</dependencies>??
- </project>????
???? 代碼中添加了dependencies元素,該元素下可以包含多個dependency元素以聲明項目的依賴,這里我們添加了一個依賴——groupId是junit,artifactId是junit,version是4.7。前面我們提到groupId、artifactId和version是任何一個Maven項目最基本的坐標,JUnit也不例外,有了這段聲明,Maven就能夠自動下載junit-4.7.jar。也許你會問,Maven從哪里下載這個jar呢?在Maven之前,我們可以去JUnit的官網下載分發包。而現在有了Maven,它會自動訪問中央倉庫(http://repo1.maven.org/maven2/),下載需要的文件。讀者也可以自己訪問該倉庫,打開路徑junit/junit/4.7/,就能看到junit-4.7.pom和junit-4.7.jar。本書第6章會詳細介紹Maven倉庫及中央倉庫。
上述POM代碼中還有一個值為test的元素scope,scope為依賴范圍,若依賴范圍為test則表示該依賴只對測試有效,換句話說,測試代碼中的import JUnit代碼是沒有問題的,但是如果我們在主代碼中用import JUnit代碼,就會造成編譯錯誤。如果不聲明依賴范圍,那么默認值就是compile,表示該依賴對主代碼和測試代碼都有效。
配置了測試依賴,接著就可以編寫測試類,回顧一下前面的HelloWorld類,現在我們要測試該類的sayHello()方法,檢查其返回值是否為“Hello Maven”。在src/test/java目錄下創建文件,其內容如代碼清單3-4:
代碼清單3-4:Hello World的測試代碼
?

- package?com.juvenxu.mvnbook.helloworld;??
- import?static?org.junit.Assert.assertEquals;??
- import?org.junit.Test;??
- ??
- public?class?HelloWorldTest??
- {??
- ????@Test??
- ????public?void?testSayHello()??
- ????{??
- ????????HelloWorld?helloWorld?=?new?HelloWorld();??
- ??
- ????????String?result?=?helloWorld.sayHello();??
- ??
- ????????assertEquals(?"Hello?Maven",?result?);??
- ????}??
- }???
一個典型的單元測試包含三個步驟:一,準備測試類及數據;二,執行要測試的行為;三,檢查結果。上述樣例中,我們首先初始化了一個要測試的HelloWorld實例,接著執行該實例的sayHello()方法并保存結果到result變量中,最后使用JUnit框架的Assert類檢查結果是否為我們期望的”Hello Maven”。在JUnit 3中,約定所有需要執行測試的方法都以test開頭,這里我們使用了JUnit 4,但我們仍然遵循這一約定,在JUnit 4中,需要執行的測試方法都應該以@Test進行標注。
測試用例編寫完畢之后就可以調用Maven執行測試,運行?mvn clean test?:
?

- [INFO]?Scanning?for?projects...??
- [INFO]?------------------------------------------------------------------------??
- [INFO]?Building?Maven?Hello?World?Project??
- [INFO]????task-segment:?[clean,?test]??
- [INFO]?------------------------------------------------------------------------??
- [INFO]?[clean:clean?{execution:?default-clean}]??
- [INFO]?Deleting?directory?D:\git-juven\mvnbook\code\hello-world\target??
- [INFO]?[resources:resources?{execution:?default-resources}]??
- …??
- Downloading:?http://repo1.maven.org/maven2/junit/junit/4.7/junit-4.7.pom??
- 1K?downloaded??(junit-4.7.pom)??
- [INFO]?[compiler:compile?{execution:?default-compile}]??
- [INFO]?Compiling?1?source?file?to?D:?\code\hello-world\target\classes??
- [INFO]?[resources:testResources?{execution:?default-testResources}]??
- …??
- Downloading:?http://repo1.maven.org/maven2/junit/junit/4.7/junit-4.7.jar??
- 226K?downloaded??(junit-4.7.jar)??
- [INFO]?[compiler:testCompile?{execution:?default-testCompile}]??
- [INFO]?Compiling?1?source?file?to?D:\?code\hello-world\target\test-classes??
- [INFO]?------------------------------------------------------------------------??
- [ERROR]?BUILD?FAILURE??
- [INFO]?------------------------------------------------------------------------??
- [INFO]?Compilation?failure??
- D:\code\hello-world\src\test\java\com\juvenxu\mvnbook\helloworld\HelloWorldTest.java:[8,5]?-source?1.3?中不支持注釋??
- (請使用?-source?5?或更高版本以啟用注釋)??
- ????@Test??
- [INFO]?------------------------------------------------------------------------??
- [INFO]?For?more?information,?run?Maven?with?the?-e?switch??
- ??…??
不幸的是構建失敗了,不過我們先耐心分析一下這段輸出(為了本書的簡潔,一些不重要的信息我用省略號略去了)。命令行輸入的是mvn clean test,而Maven實際執行的可不止這兩個任務,還有clean:clean、resources:resources、compiler:compile、resources:testResources以及compiler:testCompile。暫時我們需要了解的是,在Maven執行測試(test)之前,它會先自動執行項目主資源處理,主代碼編譯,測試資源處理,測試代碼編譯等工作,這是Maven生命周期的一個特性,本書后續章節會詳細解釋Maven的生命周期。
從輸出中我們還看到:Maven從中央倉庫下載了junit-4.7.pom和junit-4.7.jar這兩個文件到本地倉庫(~/.m2/repository)中,供所有Maven項目使用。
構建在執行compiler:testCompile任務的時候失敗了,Maven輸出提示我們需要使用-source 5或更高版本以啟動注釋,也就是前面提到的JUnit 4的@Test注解。這是Maven初學者常常會遇到的一個問題。由于歷史原因,Maven的核心插件之一compiler插件默認只支持編譯Java 1.3,因此我們需要配置該插件使其支持Java 5,見代碼清單3-5:
代碼清單3-5:配置maven-compiler-plugin支持Java 5
?

- <project>??
- …??
- ??<build>??
- ????<plugins>??
- ???????<plugin>??
- ?????????<groupId>org.apache.maven.plugins</groupId>??
- ?????????<artifactId>maven-compiler-plugin</artifactId>??
- ?????????<configuration>??
- ???????????<source>1.5</source>??
- ???????????<target>1.5</target>??
- ?????????</configuration>??
- ???????</plugin>??
- ????</plugins>??
- ??</build>??
- …??
- </project>????
該POM省略了除插件配置以外的其他部分,我們暫且不去關心插件配置的細節,只需要知道compiler插件支持Java 5的編譯。現在再執行mvn clean test,輸出如下:
?

- …??
- [INFO]?[compiler:testCompile?{execution:?default-testCompile}]??
- [INFO]?Compiling?1?source?file?to?D:?\code\hello-world\target\test-classes??
- [INFO]?[surefire:test?{execution:?default-test}]??
- [INFO]?Surefire?report?directory:?D:\code\hello-world\target\surefire-reports??
- -------------------------------------------------------??
- ?T?E?S?T?S??
- -------------------------------------------------------??
- Running?com.juvenxu.mvnbook.helloworld.HelloWorldTest??
- Tests?run:?1,?Failures:?0,?Errors:?0,?Skipped:?0,?Time?elapsed:?0.055?sec??
- Results?:??
- Tests?run:?1,?Failures:?0,?Errors:?0,?Skipped:?0??
- [INFO]?------------------------------------------------------------------------??
- [INFO]?BUILD?SUCCESSFUL??
- [INFO]?------------------------------------------------------------------------??
- …??
我們看到compiler:testCompile任務執行成功了,測試代碼通過編譯之后在target/test-classes下生成了二進制文件,緊接著surefire:test任務運行測試,surefire是Maven世界中負責執行測試的插件,這里它運行測試用例HelloWorldTest,并且輸出測試報告,顯示一共運行了多少測試,失敗了多少,出錯了多少,跳過了多少。顯然,我們的測試通過了——BUILD SUCCESSFUL。