JUnit 框架是 Java 語言單元測試當前的一站式解決方案。這個框架值得稱贊,因為它把測試驅動的開發思想介紹給 Java 開發人員并教給他們
如何有效地編寫單元測試。但是,在過去的幾年中,JUnit 的改進不大;所以,為當今復雜的環境編寫測試已經變成一個越來越困難的任務,即
JUnit 必須與其他一些補充性測試框架集成起來。在本文中,Filippo Diotalevi 介紹了 TestNG,這是一個測試 Java
應用程序的新框架。TestNG 不僅確實強大、創新、可擴展、靈活,它還展示了 Java Annotations(JDK 5.0 中的重大新特性)
的有趣應用。
在每個現代軟件包的構造階段,測試這一實踐都扮演著中心角色。過去那種先編寫代碼,然后有空的時候再測試(或者根本不測試)
的日子已經一去不返,因為大多數開發人員現在認識到需要采用編碼和測試彼此交織、同步推進的軟件方法論,以便盡早發現 bug,在開發過程開始的時候就識
別出主要的風險。
JUnit 超過了其他測試框架,推動開發人員理解了測試尤其是單元測試的用途。利用一個相當簡單、實用、嚴格的架構,
JUnit 已經能夠“傳染”大量開發人員。(有關“被測試傳染”的更多信息,請參閱 參考資料。) JUnit 用戶已經學會了單元測試的一些基本規
則:
每段代碼都必須經過測試。
只要有可能,代碼的測試必須隔離進行(例如,使用像 模擬對象 這樣的技術)。
軟件必須容易測試 ?? 也就是說,在編寫的時候要想著測試。
但
是,隨著開發人員對測試的信任增長,JUnit 的簡單性和嚴格性把他們分成兩個相反的派別。一方面,有些人堅信 JUnit 的簡單性對于不斷地提醒程
序員軟件也必須保持簡單來說是必不可少的(這稱為 KISS 原則,代表 keep it simple, stupid);另一方面,有些人認為
JUnit 不是簡單而是簡化,所以他們想要從測試框架得到新的高級特性、更大的靈活性和更強大的能力。JUnit 的一些特殊特性,就是為了滿足這個
群體的一些具體批評而推出的:
因為 Java 語言的單繼承性,所以必須擴展 TestCase 類的限制很大。
無法向 JUnit 的測試方法傳遞參數,也無法向 setUp() 和 tearDown() 方法傳遞參數。
執行模型有點奇怪:每次執行一個測試方法的時候,都要重新實例化測試類。
管理復雜項目中的不同測試套件有可能非常復雜。
TestNG 的創造者
TestNG
的創造者是 Cedric Beust,他在 Java 編程領域非常出名,是 EJB 3 專家組的成員,也是其他一些流行的開源項目(例如
EJBGen 和 Doclipse)的創造者。TestNG 在 Apache 軟件許可條款約束下發布,并可從其 Web 站點下載(請參閱 參考
資料 中到該站點和 Cedric 站點的鏈接)。
在本文中,您將學習到如何用這個叫做 TestNG 的新測試框架
為應用程序編寫單元測試。TestNG 的靈感來自 JUnit,同時盡量保持后者的簡單性;但是,TestNG 消除了老框架的大多數限制,使開發人員
可以編寫更加靈活、更加強大的測試。由于 TestNG 大量借用 Java Annotation(隨 JDK 5.0 引入;有關這個新特性的更多信
息,請參閱 參考資料)來定義測試,所以本文也可以向您演示如何在實際的生產環境中使用 Java 語言的這個新特性。
關于代碼
為
了演示 TestNG 的用法,我要為叫做 Jakarta Common Lang 的這個廣泛應用的開源庫(其中包含一些處理和操縱字符串、數字和
Java 對象的有用的類)編寫一些單元測試。在下面的 參考資料 一節中,您可以找到 TestNG 和 Jakarta Common Lang
庫的鏈接;如果您想在自己的機器上隨著本文一起練習,這二者都需要下載。
可以在兩個不同的包中得到 TestNG:一個包要求
JDK 5.0,另一個包與 Java 語言 1.4 版本兼容。定義測試的時候,它們使用的語法略有差異:前者使用 JDK 5.0 標注,后者使用
舊的 Javadoc 風格的標注。本文使用的是 JDK 5.0 版本,所以在繼續閱讀本文之前,需要對標注有基本的了解;您可以在 參考資料 中找到
關于這個主題的 developerWorks 資源的鏈接。但是,您要知道 只有在編譯和運行測試的時候 才需要 JDK 5.0,所以您仍然可以用自
己喜歡的編譯器來構建應用程序。實際上,您將用從 Jakarata 項目的 Web 站點下載的相同 JAR 文件來測試
Jakarta Common Lang 庫。關于使用 Java 平臺 1.4 版本的 TestNG 的更多細節,可以在 TestNG 的
Web 站點上找到。
最后,請單擊本文頂部或底部的 Code 圖標,下載 j-testng-sample.zip 文件,其中包
含一些示例,演示了如何用 TestNG 為 Jakarta Commons Lang 編寫單元測試。在里面,可以找到這里給出的大多數代碼,還有其
他一些示例。閱讀本文并不需要這些代碼,但是它可以幫助您更加深入地理解在這里介紹的概念。
TestNG 快速起步
TestNG
的測試類是普通的老式 Java 對象;您不需要擴展任何特殊的類,也不需要使用測試方法的任何命名約定:您只要用標注 @Test 通知框架這個類的方
法是測試。清單 1 演示了實用類 StringUtils 的一個最簡單的測試。它測試 StringUtils 的兩個方法: isEmpty()
方法檢測 String 是否為空; trim() 方法從 String 兩端刪除控制字符。請注意,其中使用了 Java 指令 assert 來檢
測錯誤情況。
清單 1. 針對類 StringUtils 的一個測試用例
package tests;
import com.beust.testng.annotations.*;
import org.apache.commons.lang.StringUtils;
public class StringUtilsTest
{
@Test
public void isEmpty()
{
assert StringUtils.isBlank(null);
assert StringUtils.isBlank("");
}
@Test
public void trim()
{
assert "foo".equals(StringUtils.trim(" foo "));
}
}
但
是,在運行測試之前,必須用特殊的 XML 文件配置 TestNG,習慣上把這個文件命名為 testng.xml。這個文件的語法非常簡單,如清單
2 所示。這個文件首先定義測試套件 My test suite,這個套件只包含一個測試 First test,這個測試由
StringUtilsTest 類完成。
清單 2. TestNG 的配置文件
<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd"; >
<suite name="My test suite">
<test name="First test">
<classes>
<class name="tests.StringUtilsTest" />
</classes>
</test>
</suite>
如
果這個示例 testng.xml 文件看起來沒什么用處(只有一個測試類),那么好消息是:這實際上是您定義測試套件時 惟一需要編寫的文件。還記得
JUnit 過去的日子么?在那些日子里,套件的定義可能分布在多個文件中:JUnit 的 TestSuite 文件,屬性文件,還有當然缺不了的
Ant 構建文件。使用 TestNG,所有必需的數據都集中在 testng.xml 文件中。不需要額外的 TestSuite 文件和構建文件。
要運行測試,請用 javac 編譯類,然后用以下命令調用 TestNG :
java -ea -classpath .;testng.jar;commons-lang-2.0.jar com.beust.testng.TestNG testng.xml
在
這里,選項 -ea 告訴 JVM 處理斷言(在斷言失敗時拋出異常);運行這個例子只需要 testng.jar 和 commons-lang-
2.0.jar 這兩個庫,而 com.beust.testng.TestNG 是 TestNG 的主類。對于所有那些已經非常高興地忘記了
java 和 javac 的神秘語法的開發人員來說,還提供了一個有用的 Ant 任務。作為例子,清單 3 演示了本文發布的示例應用程序的
Ant 構建文件。請注意與類 com.beust.testng.TestNGAntTask 關聯的 testng 任務的定義,以及它在
test 目標中相當簡單的用法。
清單 3. 帶有 TestNG 任務的 Ant 構建文件
<project name="sample" default="test" basedir=".">
<!-- COMPILE TESTS-->
<path id="cpath">
<pathelement location="testng.jar"/>
<pathelement location="commons-lang-2.0.jar"/>
</path>
<target name="compile">
<echo message="compiling tests"/>
<mkdir dir="classes"/>
<javac debug="true"
source="1.5" classpathref="cpath"
srcdir="src" destdir="classes"/>
</target>
<!-- RUN TESTS-->
<taskdef name="testng"
classname="com.beust.testng.TestNGAntTask"
classpathref="cpath"/>
<path id="runpath">
<path refid="cpath"/>
<pathelement location="classes"/>
</path>
<target name="test" depends="compile">
<echo message="running tests"/>
<testng fork="yes" classpathref="runpath" outputDir="test-output">
<fileset dir="src" includes="testng.xml"/>
<jvmarg value="-ea" />
</testng>
</target>
</project>
如
果一切正常,那么應當在控制臺中看到測試結果。而且,TestNG 還在當前目錄下自動創建了一個叫做 test-output 的文件夾,并在其中創建
了一份非常好的 HTML 報告。如果打開該報告并裝入 index.html,就可以看到與圖 1 中的頁面類似的頁面。
圖 1. TestNG 創建的 HTML 報告
定義測試組
TestNG 另外一個有趣的特性是其定義測試組的能力。每個測試方法都可以與一個或多個組相關聯,但可以選擇只運行某個測試組。要把測試加入測試組,只要把組指定為 @Test 標注的參數,使用的語法如下:
@Test(groups = {"tests.string"})
在
這個具體的例子中,您聲明:標注的方法屬于 tests.string 組。因為參數 groups 是一個數組,所以可以指定多個組,組名之間用逗號分
隔。例如,在示例應用程序中,您可以為 String、Number 以及 boolean 創建不同的測試,然后如清單 4 所示配置 TestNG,
有選擇地運行它們.
清單 4. 帶有不同組的配置文件
<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd"; >
<suite name="My suite">
<test name="Simple example">
<groups>
<run>
<include name="tests.string" />
<include name="tests.math" />
<exclude name="tests.boolean"/>
</run>
</groups>
<classes>
.... list classes here....
</classes>
</test>
</suite>
顯然,當運行不同的測試組時,HTML 報告能夠在單一列表中顯示所有測試,也可以在獨立的列表中顯示每個組的測試,從而能夠立即理解問題的來源。
配置方法
使用 TestNG,不僅可以指定測試方法,還可以用專門的標注 @Configuration 指定類中的其他特定方法,這些方法叫做 配置方法。配置方法有四種類型:
beforeTestClass 方法在類實例化之后,但是在測試方法運行之前執行。
afterTestClass 方法在類中的所有測試方法執行之后執行。
beforeTestMethod 方法在類中的任何測試方法執行之前執行。
afterTestMethod 方法在類中的每個測試方法執行之后執行。
圖 2 進一步描述了測試類的生命周期。
圖 2. 測試類的生命周期
清
單 5 演示了配置方法的一些示例。請注意,如果您使用組,那么配置方法也必須屬于某個組。而且,配置方法的四種類型彼此之間不是互斥的,所以可以把方法
定義成同時屬于一種或多種配置方法類型。(作為例子,請參閱清單 5 中的 aroundTestMethods() 方法)。
清單 5. 配置方法示例
@Configuration(beforeTestClass = true, groups = {"tests.workflow"})
public void setUp()
{
System.out.println("Initializing...");
}
@Configuration(afterTestMethod = true, beforeTestMethod = true, groups = {"tests.workflow"})
public void aroundTestMethods()
{
System.out.println("Around Test");
}
TestNG 中的配置方法是 JUnit 的 setUp() 和 tearDown() 方法的增強版;它們的主要目的是為測試創建正確的執行上下文,并在測試用例執行之后刷新數據。
異常檢測
使
用 TestNG,您可以非常簡單、非常容易地檢測異常的發生。很明顯,用 JUnit 也可以做這件事,但是正如您在清單 6 中的示例中所看到的,使
用 TestNG 的 @ExpectedExceptions 標注可以使代碼編寫驚人地容易和簡單。@ExpectedExceptions 標注指
明框架能夠容忍拋出的 NumberFormatException 異常,所以不應當被當作是故障。要查看在某行代碼中是否拋出異常,您可以直接在這行
代碼之后加入 assert false 語句。這意味著 只有 在指定行中拋出特定類型的異常的時候,您才會通過測試。
清單 6. 用 TestNG 進行異常檢測
public class NumberUtilsTest
{
@Test(groups = {"tests.math"})
@ExpectedExceptions(NumberFormatException.class)
public void test()
{
NumberUtils.createDouble("12.23.45");
assert false; //shouldn't be invoked
}
}
結束語
在本文中,我提供了 TestNG 的快速實用介紹,目的是展示如何開始編寫單元測試。但是,它不是一份完整的參考手冊。TestNG 還有許多其他非常有用的有趣特性:
可以向測試方法和配置方法傳遞參數,可以用標注或在 XML 配置文件中聲明參數。
可以在 TestNG 下用“兼容模式”運行過去運行良好的 Junit 測試。
可以在測試組之間建立依賴性,決定它們的執行順序。
要了解這個框架的所有潛力,有必要參閱 TestNG 的文檔(參閱 參考資料)。
所有這些特性,與用于定義測試的 Java 標注一起,使整個測試過程更加簡單、更加靈活。編寫測試必須遵守的規則 很少;除此之外,您絕對可以自由選擇自己喜歡的測試策略。
在使用 TestNG 時最明顯的是,這個模板已經是編寫單元測試的一個好選擇了,而且,在設計上,它與其他庫和工具的集成非常簡單,所以它未來的發展會給開發人員帶來一些有趣的新東西。
參考資料
您可以參閱本文在 developerWorks 全球站點上的 英文原文。
請單本文頂部或底部的 Code 圖標下載 j-testng-sample.zip,其中包含本文中討論的代碼。
在項目的 Web 站點 下載 TestNG。
運行本文的代碼,需要下載 Jakarta Commons Lang 庫。
如果不熟悉 Java 標注,請閱讀 Brett McLaughlin 2004 年 9 月份開始在 developerWorks 上發表的由兩部分組成的系列文章:
Tiger 中的注釋,第 1 部分: 向 Java 代碼中添加元數據
Tiger 中的注釋,第 2 部分: 定制注釋
在 JUnit.org 下載 JUnit。
Malcolm Davis 撰寫的“利用 Ant 和 JUnit 進行增量開發”(developerWorks,2000 年 11 月)是一篇很好的文章,解釋了如何把 JUnit 集成到項目中。
Eric Allen 和 Roy Miller 在他們各自的專欄 診斷 Java 代碼 和 極限編程 中都經常提及到單元測試。
Testdriven.com 是關于測試驅動開發的文章和資源的全面集合。
學習更多關于各種 開源測試工具。
學習更多關于“使用模仿對象進行單元測試”,Alexander Day Chaffee 和 William Pietri (developerWorks,2002 年 11 月)。
關于 JUnit、單元測試和 TestNG 的大量思想都記錄在 Cedric Beust's blog。
您是否 test infected?了解這個術語的意義。
請訪問 Developer Bookstore,獲得技術書籍的完整列表,其中包括數百本 Java 相關主題 的圖書。