Grester 是一種針對 Jester 的 Apache Maven 包裝器,用於檢查未采用測試驅動方式編寫的代碼
Jester 由 Ivan Moore 編寫,它是測試由程序員和開發人員編寫的單元測試的優秀工具。該工具基於這樣一個假設:代碼中的很多區域包含了條件語句、循環語句和 case 語句,並且在一些代碼區域中,類的圈復雜度(cyclomatic complexity)由於存在很多執行路徑而會突然增加。Jester 主要關注的就是類似這樣的代碼。但是要運行它,必須使用指向各個資源的格式良好的類路徑。
Grester 是針對 Jester 的 Apache Maven 包裝器,它減輕了從項目依賴關系構造 Java™ 類路徑之類的麻煩,這樣您可以使用 Jester 更輕松地測試執行點。Grester 還增強了使用 Maven 的一些優點,Maven 是其基礎架構的核心。在對沒有使用測試驅動方式編寫的代碼進行額外檢查時,Jester 尤為有用。此類代碼可以是舊式應用程序中的遺留代碼,甚至還可以是最近編寫的代碼,這些代碼的開發人員認為使用 Agile 的測試驅動方法指導構建高質量代碼來說難度較大。
實際上,您可以使用 Grester 來突破使用非測試驅動方式編寫代碼的限制。根據我的經驗,范圍蔓延(scope creep)和可能錯失或繞過實際業務函數的代碼將會增加錯誤數和惡意代碼 數量(快速處理 blob 反模式,即使在很小的代碼段中,都不必作為單個難於管理的模塊或模塊集)。
本文不會探討解釋 Jester 的輸出及精確說明 Jester 如何工作的技術細節。本文旨在探討獲取並圍繞 Jester 使用 Maven 插件包裝器。
獲取 Grester
運行 Grester 所需的基礎設施非常少:您只需要使用 Maven 就能構建和使用 Grester。Grester 是用 Groovy 編寫的,Groovy 是一種動態語言,它的語法類似於 Java 並且擁有 Python 和 Ruby 等語言的優點。Grester 本質上只是用於快速運行 Jester 工具的另一個 Maven 插件,因此 Grester 的真正威力來自 Jester。在本文中,結合使用了 Jester V1.37 與 Grester V0.3 alpha 發行版。
Jester 打破常規:為什麼呢?
如果所有項目 Java Archive (JAR) 依賴關系都位於一個位置,那麼直接運行 Jester 不可能比在 Java 類路徑條目中引用單個路徑更簡單。但是,當依賴關系散布到整個文件系統中時,每次 Jester 運行的配置問題會十分復雜而討厭,尤其是在各個依賴關系隨時間改變位置時。使用 Maven 將顯著簡化此過程。
Jester 運行在 Maven 項目構建配置以外的每個實例中。那麼,Grester 的特別之處在哪裡?答案在於 Maven 組織其依賴關系的方式。這種 “安排” 非常高效,Maven 不但嘗試把 Java(或 Groovy)JAR 和 Web 歸檔 (WAR) 的查找方式標准化,而且還嘗試把保存方式標准化。
Linux 和 UNIX 用戶注意
Grester 的文件大小非常小,並且在解壓縮後,您可以放心地刪除目前的壓縮歸檔。這裡是在 Windows 計算機中使用 Cygwin 來說明解壓縮它是多麼簡單 —— 甚至在模擬的 Linux 環境中也一樣。但是,不建議用早於 Grester 0.3 的版本在 Linux 或 UNIX 系統上試驗,因為雖然 Windows alpha 版本非常穩定,但是缺少一些操作系統功能。不過,在所有版本中,都使用 Jester V1.37。
如果您不熟悉 Maven,請使用系統庫 的概念。有一個位於 $USER_HOME\.m2\repository 的默認本地系統庫,還有一個在位於 $MAVEN_HOME/conf 的 pom.xml 或 settings.xml 文件中配置的遠程系統庫。
安裝 Grester
獲取 TAR 壓縮資源(.tar 文件和 tar.gz 文件適用於 UNIX® 和 Linux®)或者 Microsoft® Windows® ZIP 文件後,請將其解壓縮。有很多種方法可以完成此操作:在這裡,我使用 Windows 中的 Cygwin 實用程序。
圖 1. 用 Windows 中的 Cygwin 實用程序解壓縮 Grester
您還可以將 TAR 實用程序與 xzvf 選項結合使用來解壓縮 tar.gz 文件,或者與 xvf 選項結合使用來解壓縮普通 .tar 文件。圖 2 給出了該過程的示例。
圖 2. 用 TAR 實用程序解壓縮 Grester tar.gz 文件
最終的目錄結構應當類似於圖 3。
圖 3. 在 Windows 中解壓縮的 Grester
配置、構建和安裝 Grester
此時,您已經准備好讓 Maven 知道可以從哪個外部系統庫獲得相關 Grester Groovy 依賴關系,從而在本地把 Grester 編譯和安裝成 Maven 插件。您可以通過把兩個遠程系統庫添加到 $MAVEN_HOME/conf/settings.xml 文件中來完成此操作,如下所示:
清單 1. 把 Maven 指向包含 Groovy 依賴關系的遠程系統庫
<settings>
<profiles>
<profile>
<id>repositoryDefinitions</id>
<repositories>
.....
.....
<!-- You may have other repositories -->
....
....
<repository>
<id>apache-snapshotsv/id>
<name>Apache Snapshots Repository</name>
<url>http://people.apache.org/repo/m2-snapshot-repository</url>
<layout>default</layout>
<snapshots>
<enabled>true</enabled>
<updatePolicy>daily</updatePolicy>
<checksumPolicy>ignore</checksumPolicy>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</repository>
.....
.....
</repositories>
...
</profile>
</profiles>
</settings>
接下來是 Maven 的插件配置,該插件配置將指定 Grester 的 Groovy 插件依賴關系的系統庫。此插件系統庫配置放在為系統庫聲明的同一個配置文件 中(例如,名稱 repositoryDefinitions 被用作配置文件的名稱),如下所示:
清單 2. 把 Maven 指向包含 Groovy 插件依賴關系的遠程系統庫
<settings>
<profiles>
<profile>
<id>repositoryDefinitions</id>
....
....
</repositories>
<pluginRepositories>
<!-- You may have other plug-in repositories -->
....
....
<pluginRepository>
<id>apache-snapshots</id>
<name>Apache Snapshots Repository</name>
<url>http://people.apache.org/repo/m2-snapshot-repository</url>
<layout>default</layout>
<snapshots>
<enabled>true</enabled>
<updatePolicy>daily</updatePolicy>
<checksumPolicy>ignore</checksumPolicy>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</pluginRepository>
...
...
</pluginRepositories>
...
</profile>
</profiles>
</settings>
現在您終於可以構建插件直至完成。 Grester 要求使用 Maven V2.0.5 或更高版本。如果使用早期版本,則會在編譯和使用 Groovy-mojo-support 依賴關系中的功能時遇到問題。如果 $MAVEN_HOME/bin 目錄是可執行文件系統路徑的一部分,則可以從 maven-grester-plugin 目錄(包含 Grester 的 pom.xml 文件的目錄)中執行 mvn clean install 命令,如下所示:
圖 4. 從命令行構建 Grester
構建通常運行得很快(少於 20 秒)。圖 5 顯示成功安裝窗口。
圖 5. 在 Maven 的本地系統庫中安裝 Grester
Grester 的 TDD 方法
編寫 Grester 時,我必須在編寫集成測試時記住它們適用的操作系統環境。這變得有點挑戰性,因為我將從匹配操作系統類型的 if . . . else 子句開始,然後根據類型做出斷言。不久,如果為該平台編寫測試的同時 又為另一個平台編寫測試,則可以實現跨兩個平台的穩定的成功構建。
必須注意的是 Grester 安裝在 Maven 的本地系統庫中的位置。如果不熟悉 Maven,則其默認本地系統庫為 $USER_HOME/.m2/repository/。默認情況下,在運行 Windows 的計算機中,$USER_HOME 很可能被轉換為 Documents and Settings/$USERNAME/(其中 $USERNAME 是登錄的用戶)。在 Linux/UNIX 計算機中,$USER_HOME 將轉換成 /home/$USERNAME/。快速浏覽 Windows 本地系統庫可以發現 Grester 被安裝到 C:/Documents and Settings/$USERNAME/.m2/repository/org/apache/maven/plugins 中,並且創建了一個名為 maven-grester-plugin 的目錄。此目錄包含版本號目錄(最新版本是 V0.3);該目錄中有實際的 maven-grester-plugin-x.x.jar 文件。
使用此結構的原因在於 Grester 的 pom.xml 文件。如圖 6 所示,Grester 項目的 groupId 是 org.apache.maven.plugins。用包含此字符串作為 groupId 值的 Java 或 Groovy 語言編寫的所有 Maven 插件,相比擁有其他一些任意 groupId 的 Maven 插件,都包含更易於通過命令行執行的 mojo。由於 Grester 將使用此字符串,因此在通過命令行執行各個 mojo 目標時,您無需預先考慮 groupId 和 artifactId。
圖 6. Grester 的 pom.xml 配置文件中的 groupId
maven-grester-plugin 目錄是在安裝時創建的(install 目標將創建此目錄),如下所示。其他標准 Maven 插件都安裝在同一個上級目錄中,例如 maven-surefire-plugin 和 maven-install-plugin 目錄。
圖 7. Maven 的本地系統庫中的 Grester
如果項目的自定義組 ID 和工件 ID 太長且很難記或者只是重復鍵入很麻煩,那麼使用特殊的 groupId 字符串十分有利。這是默認插件(例如,maven-compiler-plugin 或 maven-surefire-plugin)的基本 Maven 目標(例如 compile、test,或者 test-compile)在執行時不需要諸如 mvn org.apache.maven.plugin:maven-compiler-plugin:2.0.2:compile 或 mvn org.apache.maven.plugin:maven-surefire-plugin:2.3:test 之類的命令的原因(只需 mvn compile 或 mvn test)。
將 Jester 安裝為 Grester 的主要依賴關系
此時,除了 Grester 的核心 —— 實際的 Jester 依賴關系之外其他內容都已就緒。Windows 和 Linux/UNIX 平台提供了兩個方便的腳本,它們可以把 Jester(即,實際的 jester-1.37.jar 文件)安裝到 Maven 的本地系統庫中。為什麼提供這些腳本?難道不能在 Maven 獲得其編譯器、安裝程序和其他插件依賴關系時從 Maven 所在的相同外部資源中下載這些腳本麼?答案是 Jester 沒有放在可公開獲得的已知 Maven 系統庫中(例如,Maven 的 Ibiblio),因此您不能用包含 Jester 的遠程系統庫來配置 Maven 的 $MAVEN_HOME/conf/settings.xml 文件(不考慮用 groupId-artifactId 版本組合安裝它的方法)。
因此,分別為 Windows 和 Linux/UNIX 提供了 install-jester.bat 和 install-jester.sh 可執行文件。如果任何一個可執行文件在任意平台上執行失敗,都可以使用如下所示的命令作為解決方法。
圖 8. 建立 Jester 依賴關系
注:我寫完這篇文章後,Grester V1.0.1 被發布到 Maven 公共系統庫 中。這種持續不斷地改進意味著您現在可以直接從著名的 Apache 系統庫中獲得插件,但是仍然需要有提供結合功能的 Jester 核心 JAR 和指令才能構成完整的架構。
在示例 Maven 項目中使用 Grester
那麼,您已經得到了一個精心編織的 Maven 項目,並且希望在單元測試(或者至少在一組測試中)中測試 Jester。無論是單元測試還是集成測試,明智的做法是要麼把項目復制到文件系統中的某個位置並對該副本運行 Jester,要麼使用現有副本,但是要准備好恢復對代碼源文件所做的所有更改。這是因為 Jester 將更改現有的源代碼文件,保存更改並重新編譯代碼(保留同一個目錄中的類文件作為源文件)。如果項目擁有的代碼庫相對較小或者所選測試很少,則可以使用現有代碼庫副本。
在 Eclipse 中設置示例文件
以測試為例,您將使用在 Eclipse IDE 中構造和准備的基本 Maven 項目。圖 9 演示了 Eclipse IDE 內的項目。
圖 9. Eclipse IDE 中的示例 Maven 項目
舉例來說,在項目中使用一個相對簡單的類和測試類。該類將使用 Java 語言處理外部過程命令的執行。清單 3 顯示了測試的類的主要部分。
清單 3. 在 Maven 項目中測試的示例類
package com.prometheus.run;
import java.io.IOException;
import java.io.InputStream;
public class CommandExecutor extends Executor{
...
public String executeCommand(String command){
...
try {
Process child = performCommandExecution(command);
stream = child.getInputStream();
sb = processStream(stream);
...
}
...
return sb.toString();
}
protected StringBuffer processStream(InputStream stream) throws IOException {
...
sb = new StringBuffer();
while ((c = stream.read()) != -1) {
sb.append((char)c);
}
return sb;
}
...
}
在 CommandExecutor 類中,executeCommand() 方法將調用同一個類 processStream() 中的受保護方法。在 processStream() 方法中,將在 while() 循環中創建一個新 StringBuffer 實例並且處理 InputStream。清單 4 顯示了測試類,還顯示了測試的主要部分。
清單 4. Maven 項目中的示例測試類
package com.prometheus.run;
import com.prometheus.run.CommandExecutor;
...
public class CommandExecutorTest extends TestCase {
...
public class MockProcess extends Process{
...
public InputStream getInputStream(){
String source= "This is a mock string";
return new ByteArrayInputStream(source.getBytes());
}
public OutputStream getOutputStream(){
return null;
}
public int waitFor(){
return 1;
}
}
public void testExecuteCommmand(){
String expected = "This is a mock string";
String actual = commandExecutor.executeCommand("lsmod");
assertEquals(expected, actual);
...
}
}
測試類 CommandExecutorTest 相對簡單。雖然給出的詳細信息不多,但是此單元測試的基本目標是在測試時通過類的 performCommandExecution() 方法調用來模擬 Process 類的行為。
必須注意的是,要讓 Grester 成功運行,項目必須編譯代碼源文件和測試源文件並成功運行任意一個測試和所有測試(注:由於這個原因,test-compile Maven 階段將標記允許 Grester 運行且不能提前運行的階段)。下一步是簡單地在項目的 pom.xml 文件中附加 Grester 的 Maven 插件配置。此配置放在 pom.xml 文件的默認構建部分中或任何常規的 Maven 配置文件中。
把 Grester 與項目聯系在一起
清單 5 顯示了放在示例項目的 pom.xml 文件中的 Grester 插件的示例配置。注意,groupId 要對應於 org.apache.Maven.plugins 並且版本應該是最新的 Grester 插件:V0.3。
清單 5. 示例項目中的 Grester 插件配置
<plugins>
...
...
<!-- START MAVEN GRESTER PLUG-IN CONFIGURATION -->
<plugin>
<groupId>org.apache.Maven.plugins</groupId>
<artifactId>Maven-Grester-plugin</artifactId>
<version>0.3</version>
<configuration>
<codeSources>src/main/java/com/prometheus/run</codeSources>
<testSuiteClass>com.prometheus.run.CommandExecutorTest</testSuiteClass>
</configuration>
<executions>
<execution>
<id>inspectSourcesCodeWithGrester</id>
<phase>test</phase>
<goals>
<goal>inspect</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- END MAVEN GRESTER PLUG-IN CONFIGURATION -->
...
</plugins>
注意,項目已被設為在 Maven 的測試階段運行 Grester 的 inspect 目標。codeSources 將指向包含測試類 CommandExecutorTest 的源代碼的目錄。它可以像簡單地指向實際類 CommandExecutor 一樣排除文件擴展名。在 Grester 附帶的 README.txt 文件中提到了擴展名 .Groovy,但是應當注意的是,目前沒有對 Grester 的支持。
從 V0.3 alpha 開始,可以在所有有效的 Maven 生命周期階段中作為插件執行的 Grester 有兩個主要目標(使用時全小寫):
inspect —— 這是 Grester 的主要目標,通常在測試階段(雖然嚴格來說,它可以是測試編譯階段之後的任意階段)執行。Grester 將通過 pom.xml 文件中列出的依賴關系創建一個可變的 Java 類路徑並把新類路徑提供給 Jester。
help —— 此目標主要用於對正確插件語法和結構的參考,可以在命令行中輸入 mvn grester:help 單獨執行。
對示例項目運行 Grester
運行簡單的 mvn clean install 命令(或者包含 inspect 目標使用的特定狀態的所有生命周期命令)將生成如下所示的輸出。
圖 10. Jester 在處理示例代碼
通過進一步檢查,您可以看到初始類文件 CommandExecutor 中的第 27 行已經從 -1 更改為 1。Jester 對單個類執行一個完整操作需要花費一些時間。在操作結束時將生成 jesterReport.xml 文件,該文件顯示在 Java Swing 窗口中所發生情況的匯總詳細信息。
尋求 Grester 幫助
通過命令行運行 mvn grester:help 將生成類似於圖 11 的輸出。它將用作配置 Grester 的簡短指南,而無需參考初始的 README.txt 文件。
圖 11. Grester 的幫助目標
結束語
Grester 不是完美的插件,並且仍然在改進中。對 Groovy 源代碼的直接支持特別有幫助。同樣的概念可以應用到不使用 Maven 但需要構造 Java 類路徑字符串(例如,跨越單個文件系統中的多個目錄列出依賴關系的 Apache Ant 構建文件)的項目。如果 Ant 文件本身已經被分成許多獨立的文件,那麼該過程可能會更加復雜。
對於在一個位置中無法輕松識別其依賴關系的項目,運行單個工具 (Jester) 所帶來的麻煩是否值得您去承受。但是,我仍然覺得 Jester 是用於考察開發人員編寫測試的方法是否具有健壯性的重要工具。確實,對於使用一組靜態測試即可找出的重大代碼庫更改,當 Jester 報告顯示出很差的單元測試或集成測試性能時,開發人員的測試驅動開發(Test-Driven Development,TDD)和行為驅動開發(Behaviour-Driven Development,BDD)技能會讓人產生懷疑。