難度級別: 中 Elliotte Harold (elharo@metalab.unc.edu), Adjunct Professor, Polytechnic University
2005-9-15 (譯) 原文:http://www-128.ibm.com/developerworks/java/library/j-junit4.html JUnit 是JAVA語(yǔ)言事實(shí)上的標準測試庫。JUnit 4是三年以來(lái)最具里程碑意義的一次發(fā)布。它的新特性主要是針對JAVA5中的標記(annotation)來(lái)簡(jiǎn)化測試,而不是利用子類(lèi)、反射或命名機制。本文將講述如何使用JUnit 4,當前前提是你最好具有JUnit的使用經(jīng)驗. JUnit, 由Kent Beck?和 Erich Gamma開(kāi)發(fā),幾乎是JAVA開(kāi)發(fā)最重要的第三方工具。正如Martin Fowler 所說(shuō),“在軟件開(kāi)發(fā)領(lǐng)域,從來(lái)就沒(méi)有如此少的代碼起到了如此重要的作用“。由于JUnit,JAVA代碼變得更健壯,更可靠,BUG也比以前更少。由于JUnit (由Smalltalk‘s的SUnit得來(lái)) 的出現,隨后產(chǎn)生了許多xUnit的測試工具,如nUnit (.NET), pyUnit (Python), CppUnit (C++), dUnit (Delphi) 和其它不同平臺及語(yǔ)言的測試相關(guān)的工具。 雖然JUnit也只是一個(gè)工具,但其產(chǎn)生的思想和技術(shù)卻較其架構更意義重大。單元測試,測試先行的編程方式,測試驅動(dòng)的開(kāi)發(fā)方式,并非必須由JUNIT實(shí)現,也不一定要用SWing實(shí)現GUI界面。JUNIT最近的一次更新是在三年前,但它比其它大多數有BUG的框架都要健壯,更重要的是,JAVA一直在改進(jìn)?,F在JAVA支持泛型,枚舉,可變長(cháng)度參數,以及標記語(yǔ)言(開(kāi)創(chuàng )了開(kāi)發(fā)可重用框架的新局面)。 JUnit‘s的停滯不前使得那些想要變革的開(kāi)發(fā)人員換其它測試工具.挑戰者有Bill Venners的Artima SuiteRunner和Cedric Beust的TestNG.這些工具庫雖然有值得推薦的功能,但沒(méi)有任何一款的地位能與JUNIT相比,沒(méi)有任何一款工具被其它業(yè)界產(chǎn)品如Ant, Maven, Eclipse廣泛支持.因此Beck 和Gamma雙開(kāi)始利用JAVA5的新特性來(lái)開(kāi)發(fā)新版的JUNIT,目的是利用JAVA5中的標記特性使得單元測試開(kāi)發(fā)更容易。Beck說(shuō):“JUNIT4的主要目的是通過(guò)簡(jiǎn)化JUNIT的使用鼓勵更多的開(kāi)發(fā)人員寫(xiě)更多的測試”。雖然會(huì )與以前的版本兼容,但JUNIT4與從JUNIT1.0就開(kāi)始的版本相比會(huì )有一個(gè)非常大的變化. 注意: 修改基本框架是一把雙刃劍,雖然JUNIT4的目的是清晰的,但細節仍有許多不同,因此本文只是一個(gè)簡(jiǎn)單的介紹,并不是最終文檔. 測試方法 以前所有版本的JUNIT都使用命名機制和反射來(lái)定位測試,下面的代碼測試1+1= 2: import junit.framework.TestCase;public class AdditionTest extends TestCase { private int x = 1; private int y = 1; public void testAddition() { int z = x + y; assertEquals(2, z); }}
|
而在JUNIT 4中,測試方法由@Test 標記說(shuō)明,如下: import org.junit.Test;import junit.framework.TestCase;public class AdditionTest extends TestCase { private int x = 1; private int y = 1; @Test public void testAddition() { int z = x + y; assertEquals(2, z); }}
|
使用標記的好處是你不用將所有測試方法命名為 testFoo(), testBar()等等以"test"開(kāi)頭的方法,下面的方法也同樣可以工作: import org.junit.Test;import junit.framework.TestCase;public class AdditionTest extends TestCase { private int x = 1; private int y = 1; @Test public void additionTest() { int z = x + y; assertEquals(2, z); }}
|
下面的代碼也同樣正確: import org.junit.Test;import junit.framework.TestCase;public class AdditionTest extends TestCase { private int x = 1; private int y = 1; @Test public void addition() { int z = x + y; assertEquals(2, z); }}
|
這種命名機制最大的優(yōu)點(diǎn)是更適合你的待測試類(lèi)或方法名稱(chēng),例如,你可以使用ListTEst.contains()測試 List.contains();使用ListTest.addAll()測試 List.add()等等. TestCase還可以繼續使用,但你沒(méi)有必須再擴展為子類(lèi),只要你聲明了@Test,你可以將測試方法放在任何類(lèi)中,當然如要訪(fǎng)問(wèn)assert等方法,你必須要引用junit.Assert類(lèi),如下:
import org.junit.Assert;public class AdditionTest { private int x = 1; private int y = 1; @Test public void addition() { int z = x + y; Assert.assertEquals(2, z); }}
|
你也可以使用JDK5中的新特性(static import)使得跟以前版本一樣簡(jiǎn)單: import static org.junit.Assert.assertEquals;public class AdditionTest { private int x = 1; private int y = 1; @Test public void addition() { int z = x + y; assertEquals(2, z); }}
|
這種方法測試受保護的方法非常容易,因為你可以在測試類(lèi)中繼承有受保護方法的類(lèi).
SetUp 和TearDown JUnit 3 中test runners 會(huì )在每個(gè)測試之前自動(dòng)調用 setUp()方法。此方法主要用于初始化變量,打開(kāi)日志,重置環(huán)境變量等。下面是XOM‘s XSLTransformTest中的 setUp()方法: protected void setUp() { System.setErr(new PrintStream(new ByteArrayOutputStream())); inputDir = new File("data"); inputDir = new File(inputDir, "xslt"); inputDir = new File(inputDir, "input"); }
|
在JUnit 4中,你仍然可以在每個(gè)測試前初始化變量和配置環(huán)境,,然而,這些操作可以不用在Setup()中完成,你可以在初始化方法前面添加@Beforer 來(lái)表示,如下: @Before protected void initialize() { System.setErr(new PrintStream(new ByteArrayOutputStream())); inputDir = new File("data"); inputDir = new File(inputDir, "xslt"); inputDir = new File(inputDir, "input"); }
|
你也可以有多個(gè)方法標記有@Before,所有方法都會(huì )在每個(gè)測試之前執行: @Before protected void findTestDataDirectory() { inputDir = new File("data"); inputDir = new File(inputDir, "xslt"); inputDir = new File(inputDir, "input");} @Before protected void redirectStderr() { System.setErr(new PrintStream(new ByteArrayOutputStream()));}
|
清除環(huán)境與JUNIT3 差不多,在JUNIT3中使用 tearDown()方法,下面的代碼是結束測試時(shí)回收內存: protected void tearDown() { doc = null; System.gc(); }
|
在JUnit 4中,你還可以使用@After標記來(lái)說(shuō)明: @After protected void disposeDocument() { doc = null; System.gc(); }
|
與 @Before一樣,你也可以有多個(gè)標記有 @After的清除方法,每個(gè)都會(huì )在執行完每個(gè)測試后執行。 最后,你不需要在父類(lèi)中明確調用這些初始化或清除方法.test runner會(huì )自動(dòng)調用這些標記的方法.子類(lèi)中的@Before方法在父類(lèi)的@Before方法之后執行(這與構造函數的執行順序一樣),而@After方法剛好相反,子類(lèi)中的@After方法先執行.然而,多個(gè)@Before和@After方法的執行順序就是未知的. 測試集范圍的初始化 JUnit 4中引入了一項JUNIT3沒(méi)有的新特性,類(lèi)級別的setUp()和tearDown(),即在一個(gè)類(lèi)的所有測試前執行初始化,并在所有測試完成后執行清除。 例如,一個(gè)測試類(lèi)中的每個(gè)測試都要用到一個(gè)數據庫連接或網(wǎng)絡(luò )連接,或其它很耗資源初始化或釋放的資源,用不著(zhù)在每個(gè)測試方法前后進(jìn)行操作,而只需要在測試類(lèi)開(kāi)始前后執行即可。下面的示例是使用第三方的庫進(jìn)行錯誤,在執行所有測試前將錯誤先重定向到非標準輸出,然后在所有測試結束后再輸出到需要的地方,這樣就不會(huì )影響到測試過(guò)程中產(chǎn)生的其它信息。 // This class tests a lot of error conditions, which// Xalan annoyingly logs to System.err. This hides System.err // before each test and restores it after each test.private PrintStream systemErr; @BeforeClass protected void redirectStderr() { systemErr = System.err; // Hold on to the original value System.setErr(new PrintStream(new ByteArrayOutputStream()));} @AfterClass protected void tearDown() { // restore the original value System.setErr(systemErr);}
|
上面的操作沒(méi)有必須在每個(gè)測試前后執行。然而要注意的是,這種方法可能影響測試間的結果,如果一個(gè)測試改變了初始化的對象,而這個(gè)對象可能是其它測試的輸入,那么測試的結果可能不正確,這種方法將依賴(lài)測試的順序并可能引入BUG。當優(yōu)化測試性能,并且當你改進(jìn)了配置和基準測試后而仍然很慢時(shí),如數據庫連接或網(wǎng)絡(luò )問(wèn)題,你才需要考慮使用這種方法。只有這樣,你才能每天執行多次測試。
異常測試 異常測試是JUNIT4中的最大的改進(jìn),以前異常測試是通過(guò)try catch實(shí)現,當拋出異常時(shí),在try的最后添加一條fail()語(yǔ)句實(shí)現.如下: public void testDivisionByZero() { try { int n = 2 / 0; fail("Divided by zero!"); } catch (ArithmeticException success) { assertNotNull(success.getMessage()); } }
|
這種方法不僅難看,而且造成無(wú)論成功或失敗,代碼覆蓋工具都不能執行某些代碼.而在JUnit 4中,你可以在要拋出異常的代碼中添加標記來(lái)聲明一個(gè)異常是期望的: @Test(expected=ArithmeticException.class) public void divideByZero() { int n = 2 / 0;}
|
如果沒(méi)有異常拋出,上面的測試則會(huì )失敗,如果你想知道異常的詳細信息或其它情況,你還是要使用try catch才行
需要忽略的測試 也許你有些測試需要很長(cháng)時(shí)間才能執行完成,并非是這個(gè)測試應該跑得快,而是它做的很復雜和很慢的工作造成的.如訪(fǎng)問(wèn)遠程網(wǎng)絡(luò )錯誤,需要很久才能有反饋.如果你不想讓這種測試破壞你整個(gè)測試過(guò)程,你可能想跳過(guò)這個(gè)測試.當然也有可能某個(gè)測試超出控制范圍而失?。鏦3C XInclude測試集中自動(dòng)識別一些JAVA不支持的Unicode代碼.為了防止這些測試總是通不過(guò),可以使用標記 @Ignore跳過(guò)這些測,如下: // Java doesn‘t yet support the UTF-32BE and UTF32LE encodings @Ignore public void testUTF32BE() throws ParsingException, IOException, XIncludeException { File input = new File( "data/xinclude/input/UTF32BE.xml" ); Document doc = builder.build(input); Document result = XIncluder.resolve(doc); Document expectedResult = builder.build( new File(outputDir, "UTF32BE.xml") ); assertEquals(expectedResult, result); }
|
test runner不會(huì )執行這些測試,但會(huì )說(shuō)明這些測試被跳過(guò)了。在命令行測試界面中,字母“I”會(huì )表示測試跳過(guò),或“E”表示測試失敗,而不是用點(diǎn)”."表示成功. $ java -classpath .:junit.jar org.junit.runner.JUnitCore nu.xom.tests.XIncludeTestJUnit version 4.0rc1.....I..Time: 1.149OK (7 tests)
|
要注意的是,假設這些測試由于某種理由放在最開(kāi)始,如果你以后一直忽略這些測試,那些需要被測試的代碼可能有問(wèn)題而不會(huì )被檢測到。因此忽略測試只是一個(gè)臨時(shí)解決方法,并不是一個(gè)解決任何問(wèn)題的真正辦法。
時(shí)間測試 性能測試是單元測試中最頭疼的問(wèn)題,JUnit 4也未完全解決此問(wèn)題, 你可以在JUNIT4的測試方法中添加一個(gè)時(shí)間參數。如果測試時(shí)間超過(guò)參數,則測試失敗。如下,如果測試時(shí)間超過(guò)0.5秒,則此測試失?。?/p> @Test(timeout=500) public void retrieveAllElementsInDocument() { doc.query("http://*");}
|
除基準性能測試外,時(shí)間測試在網(wǎng)絡(luò )測試方面也很有用.如果一個(gè)遠端的主機或數據當掉或太慢,你可以跳過(guò)此測試而不用阻塞在這里,好的測試集可以在作了一些改動(dòng)后很快的一遍一遍的執行,可能一天數十次.設置一個(gè)超時(shí)讓測試更快的執行,下面的示例中如果分析http://www.ibiblio.org/xml 的時(shí)間超過(guò)2秒,則測試失?。?/p> @Test(timeout=2000) public void remoteBaseRelativeResolutionWithDirectory() throws IOException, ParsingException { builder.build("http://www.ibiblio.org/xml"); }
|
新的斷言 JUnit 4 增加了兩上斷文方法用于比較數組: public static void assertEquals(Object[] expected, Object[] actual)public static void assertEquals(String message, Object[] expected, Object[] actual)
|
這兩個(gè)方法采用最直接方法比較,如果數組長(cháng)度相同,且每個(gè)對應的元素相同,則比較成功,否則不成功.參數為空的情況也作了考慮.
需要補充的地方 JUnit 4是一個(gè)非?;镜目蚣?,還不是以前版本的升級。JUNIT3的開(kāi)發(fā)人員會(huì )發(fā)現有些功能沒(méi)有。 - 最大的特點(diǎn)就是沒(méi)有GUI測試界面,當測試正確時(shí)是綠色條,而出錯時(shí)紅色的,你也可以在Eclipse中集成JUNIT使用,但JUNIT4既沒(méi)有AWT也沒(méi)有SWING的GUI測試界面;
- 另一個(gè)讓人吃驚的是失?。ㄆ谕e誤)和錯誤(未預計的異常錯誤)沒(méi)有明顯區別,在JUNIT3中開(kāi)發(fā)人員可以區分這兩種情況,而在JUNIT4中不行;
- 最后一個(gè)特點(diǎn)是JUNIT中沒(méi)有用于建立一堆測試類(lèi)的
suite()方法,取而代之的是,采用變長(cháng)參數傳遞未知數量的測試給test runner。
沒(méi)有GUI測試界面的確不方便,但其它改變簡(jiǎn)化了JUNIT的使用,從當前JUNIT的操作手冊和FAQ的數量就知道,而JUNIT4的文檔將不會(huì )需要這么多。
編譯和運行JUnit 4 現在JUnit 4還沒(méi)有發(fā)布編譯版本,如果想體驗版本的樂(lè )趣,則需要從CVS中獲取源代碼。分支標簽是"Version4" (see Resources ).要注意的是大部分文檔是根據JUNIT3編寫(xiě)的,還未同步更新。需要Java 5才能編譯JUnit 4,因為大量使用了標記,泛型其其它JDK5中的新特性。 執行測試的命令行方式與JUNIT3有點(diǎn)區別,你現在要使用 org.junit.runner.JUnitCore 類(lèi)進(jìn)行測試,如下: $ java -classpath .:junit.jar org.junit.runner.JUnitCore TestA TestB TestC...JUnit version 4.0rc1Time: 0.003OK (0 tests)
|
兼容性 Beck 和Gamma在努力保持后向和前向兼容性。JUnit 4可以直接運行根據JUNIT3編寫(xiě)的測試類(lèi),而不用任何修改,直接將各測試類(lèi)的全名傳遞給test runner即可.test runner會(huì )根據不同的測試類(lèi)調用不同的測試框架版本. 后向兼容性有點(diǎn)麻煩,即在JUNIT3中執行根據JUNIT4寫(xiě)的測試類(lèi),之所以要這樣是因為在一個(gè)集成環(huán)境如Ecplise中,不需要升級到JUNIT4也可以測試JUNIT4的測試類(lèi),從而避免工具IDE的升級。為了讓JUNIT4的測試類(lèi)在JUNI3中能執行,你需要一個(gè)適配類(lèi)JUnit4TestAdapter封裝JUNIT3的測試類(lèi),如下代碼: public static junit.framework.Test suite() { return new JUnit4TestAdapter(AssertionTest.class); }
|
而JAVA方面,JUNIT4一點(diǎn)兼容性都沒(méi)有,因為完全依賴(lài)于JDK5的新特性,因此不可能在JAVA1.4上面執行JUNIT4。
還有... JUnit 4遠未結束,還有許多需要補充,如文檔,現在不推薦將以前的測試類(lèi)升級到JUNIT4。當然JUNIT4的開(kāi)發(fā)速度很快,其計劃也很快會(huì )實(shí)現,JAVA1.4的開(kāi)發(fā)人員仍然可以使用JUNIT3.8,而使用JAVA5的人員可以考慮是否采用JUNIT4了,因為在特性上更適合。
資源 - Pragmatic Unit Testing in Java with JUnit (Andy Hunt and Dave Thomas, Pragmatic Programmers, 2003): An excellent introduction to unit testing.
- JUnit Recipes: Practical Methods for Programmer Testing (J. B. Rainsberger, Manning, 2004): One of the most widely cited and referenced books on JUnit.
- TestNG: Cedric Beust‘s framework pioneered the annotation based testing style now used in JUnit 4.
- "TestNG makes Java unit testing a breeze" (Filippo Diotalevi, developerWorks, January 2005): An introduction to TestNG.
- "Incremental development with Ant and JUnit" (Malcolm Davis, developerWorks, November 2000): Explores the benefits of unit testing, in particular using Ant and JUnit, with code samples.
- "Demystifying Extreme Programming: Test-driven programming" (Roy Miller, developerWorks, April 2003): Find out how test-driven programming can revolutionize your productivity and quality as a programmer, and learn the mechanics of writing tests.
- "Keeping critters out of your code" (David Carew, et. al., developerWorks, June 2003): Learn how you can use JUnit in conjunction with WebSphere Application Developer.
- "Measure test coverage with Cobertura" (Elliotte Rusty Harold, developerWorks, May 2005): Learn to identify untested code and locate bugs with this handy open source tool.
下載測試版本
- JUnit 4: Download the newest version of JUnit the SourceForge CVS repository; be sure to use the branch tag "Version4."
|