JUnit單元測試解析
1.首先看看什么是JUnit:
JUnit官網對JUnit的簡單解釋:
JUnit is a simple framework to write repeatable tests. It is an instance of the xUnit architecture for unit testing frameworks.
JUnit是一個簡單的可復用的測試框架,是xUnit測試框架的一個子集。
xUnit又是什么呢?
xUnit是一套基於測試驅動開發的測試框架。
xUnit包括:PythonUnit、CppUnit等等。JUnit應該算是xUnit家族最成功的一個了。
2.為什么要使用JUnit
使用JUnit能夠幫助我們減少在開發過程中的錯誤,把Bug扼殺在萌芽之中,有利於代碼的后期維護和檢查。做好了單元測試可以縮短開發周期,提高代碼質量。這樣我們就可以把更多的時間用到我們應該干的事情上來而不是去解決項目后期發現的越來越多的令人頭疼的問題。
測試不是用來證明你是對的,而是用來證明你沒有錯。
3.下載安裝JUnit
JUnit官網:http://junit.org/
首先訪問JUnit官網,找到welcome模塊:
然后點擊下載相應版本的JUnit就OK了。
4.使用JUnit(這里使用的JUnit4,IDE為MyEclipse)
4.1首先創建一個使用JUnit進行單元測試的Java項目:
准守一定的規則會使我們的項目和代碼看起來更加的和諧。
一般來說規則是這樣的:
a. 在Java項目(這里為JUnitTest)中創建一個名為test的source folder。
b. 在test目錄中創建和src目錄下相同的包名,在其中存放對應的測試類。
c. 不要忘了引入JUnit及其依賴jar包。
等項目完成之后把test測試源碼文件刪除即可,不影響其他的代碼。
項目的結構目錄看起來應該是這樣的:
這樣我們就可以來進行測試代碼的編寫了。
4.2一個簡單的例子
a.首先在src/util包下創建Calculator.java,編寫一個簡單的加法運算器。
package util; /** *被測試的類 *@author wxisme *@time 2015-9-2 上午9:52:24 */ public class Calculator { public int add(int a, int b) { return a + b; } }
b. 在test/util包下創建CalculatorTest.java作為Calculator的測試類。
package util; import junit.framework.Assert; import org.junit.Before; import org.junit.Test; import util.Calculator; /** * *@author wxisme *@time 2015-9-1 下午8:44:15 */ public class CalculatorTest { private static Calculator calculator; @BeforeClass public static void BuildCalculator() { calculator = new Calculator(); } @Test public void testAdd() { Assert.assertEquals(8, calculator.add(3, 5)); } }
上面的程序JUnit4提供的注解和方法。
首先看@Test注解,見名知意,是標注testAdd()方法為一個測試方法。
Assert.assertEquals(8, calculator.add(3, 5));方法是JUnit中Assert類提供的斷言方法。能夠判斷我們的預期結果和程序的輸出結果是否是一致的。
我們來測試一下:選中testAdd()方法,右鍵選擇run as JUnit test:
上面程序運行的結果應該是這樣的:
看到有綠色的條,並且Error和Failure的值都為零,說明我們的測試通過。程序運行結果與我們所期望的是一致的。
那如果把testAdd()方法中的斷言改成:
Assert.assertEquals(8, calculator.add(3, 5));
運行結果會是怎樣的呢?
應該是這樣的:

從紅色圈起來的地方我們可以得知,測試沒有通過,並且錯誤在於,我們期望的值為7,但是程序的輸出結果為8.
c. 通過上面的例子我們可以有一下總結:
測試方法必須使用@Test修飾
測試方法必須使用public void修飾
新建test源代碼目錄來存放測試代碼
測試類的包名應該和被測試類的報名保持一致
測試單元中的每個方法必須可以獨立測試,測試方法之間不能有任何依賴
測試類一般使用Test作為后綴
測試方法一般使用test作為前綴
4.3系統的來看JUnit中注解和某些方法的用法
JUnit4中提供的常用注解大致包括:
@BeforeClass
@AfterClass
@Before
@After
@Test
@Ignore
@RunWith
我們通過一個例子來了解他們的用法:
編寫一個MethodTest來測試注解的用法。
package util; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; /** * *@author wxisme *@time 2015-9-2 上午11:28:29 */ public class MethodTest { @BeforeClass //用static修飾 public static void setUpBeforeClass() throws Exception { System.out.println("beforeClass..."); } @AfterClass //用static修飾 public static void tearDownAfterClass() throws Exception { System.out.println("afterClass..."); } @Before public void setUp() throws Exception { System.out.println("before..."); } @After public void tearDown() throws Exception { System.out.println("after..."); } @Test public void test1() { System.out.println("test1..."); // fail("Not yet implemented"); } @Test public void test2() { System.out.println("test2..."); // fail("Not yet implemented"); } }
測序的運行后再Console的中輸出如下:
beforeClass...
before...
test1...
after...
before...
test2...
after...
afterClass...
其中我們可以總結出這幾個注解的執行順序和作用:
@Test將一個普通的方法修飾為一個測試方法
@BeforeClass會在所有方法運行前執行,static修飾
@AfterClass會在所有方法運行結束后執行,static修飾
@Before會在每個測試方法運行前執行一次
@After會在每個測試方法運行后被執行一次
這就是為什么在第一個例子中我會這么干了(所有的測試方法只使用一個Calculator就行了):
private Calculator calculator; @BeforeClass public static void BuildCalculator() { calculator = new Calculator(); }
@Test注解還可以帶有參數。像這樣:
@Test(expected=xx.class)
@Test(timeout=毫秒)
timeout參數很容易理解,就是指定程序運行的超時時間,單位為毫秒。
我們來看一下expected參數。
看一個例子:
在第一個例子里面加一個Exception異常屬性,在testAdd()方法中拋出。
package util; import junit.framework.Assert; import org.junit.Before; import org.junit.Test; import util.Calculator; /** * *@author wxisme *@time 2015-9-1 下午8:44:15 */ public class CalculatorTest { private static Calculator calculator; private static Exception ex; @BeforeClass public static void BuildCalculator() { calculator = new Calculator(); ex = new Exception("手動拋出的異常!"); } @Test public void testAdd() throws Exception { Assert.assertEquals(8, calculator.add(3, 5)); throw ex; } }
結果會是這樣的:
發現測試沒有通過,並且拋出了一個異常,使我們手動拋出的異常。
那么把上面的程序總@Test注解加上這樣的參數:
@Test(expected=Exception.class)
再看結果:
發現測試通過了。
是不是突然就明白expected參數的作用了呢?
其他幾個注解的功能可以類比。
再來看@Ignore和@RunWith
@Ignore見名知意,作用是讓測試運行器忽略其修飾的測試方法。@Ignore所修飾的方法不會執行。
自行測試。
@RunWith可以用來更改運行測試器org.junit.runner.Runner。
下面的兩個例子都是通過@RunWith注解來實現的。
1)測試套件
試想如果項目中有100個測試類需要測試,你會怎么做呢?每個類運行一次?運行100次?
JUnit當然不會只允許有這一個愚蠢的方法。答案就是測試套件。
測試套件用來組織批量運行測試類。看一個例子。
首先來創建3個測試類模擬需要批量運行的測試類群。
public class DemoTest1 { @Test public void test() { System.out.println("DemoTest1..."); } } public class DemoTest2 { @Test public void test() { System.out.println("DemoTest2..."); } } public class DemoTest3 { @Test public void test() { System.out.println("DemoTest3..."); } }
然后創建一個名為SuiteTest的測試類,並加上@RunWith注解和@Suite.SuiteClasses注解。(該類中沒有其他測試方法)
package util; import org.junit.runner.RunWith; import org.junit.runners.Suite; /** *測試套件 *@author wxisme *@time 2015-9-2 下午7:37:56 */ @RunWith(Suite.class) @Suite.SuiteClasses({DemoTest1.class,DemoTest2.class,DemoTest3.class}) public class SuiteTest { }
測試程序總首先用@RunWith來修改測試運行器。
然后使用@Suite.SuiteClasses來指定要批量執行的測試類(數組的形式)
程序的運行結果應該是這樣的(通過測試並且在Console中輸出):
可見我們的批量運行生效了。
2)JUnit參數化設置
再試想一個場景:如果一個測試方法中需要測試100組數據是否和期望值一致,你會怎么做呢?手動copy+change100次?
JUnit也不會只允許使用這么愚蠢的方法。這就用到了JUnit的參數化設置。
看一個例子:
a.首先創建一個名為ParameterTest的測試類,並用RunWith注解來改變測試運行器。
b.聲明變量來存放預期值和結果值。
c.聲明一個返回值為Collection的公共靜態方法,並使用 @Parameters進行修飾。
d.為測試類聲明一個帶有參數的公共構造方法,並在其中為之聲明變量賦值。
代碼應該是這樣的:
package util; import java.util.Arrays; import java.util.Collection; import junit.framework.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; /** *JUnit參數化設置 *@author wxisme *@time 2015-9-2 下午8:03:47 */ @RunWith(Parameterized.class) public class ParameterTest { private static Calculator calculator; @BeforeClass public static void setUpBeforeClass() { calculator = new Calculator(); } private int expected; private int input1; private int input2; @Parameters public static Collection<Object[]> setParameters() { Object[][] objs = {{8,3,5},{5,3,2}}; return Arrays.asList(objs); } public ParameterTest(int expected, int input1, int input2) { this.expected = expected; this.input1 = input1; this.input2 = input2; } @Test public void testParameters() { Assert.assertEquals(expected, calculator.add(input1, input2)); } }
然后我們運行這個測試類(要在類的級別上運行,如果在方法上運行就會有初始化錯誤)。
結果應該是這樣的:
運行結果顯示兩組測試數據均測試通過。
到此JUnit的基本用法就介紹完了,關於JUnit的其他用法以及斷言的API請參考官方提供的document(如果你不想自己找的話,在評論區留下郵箱,我會發給你的哦)。
5.總結
JUnit帶給我們的不僅是開發效率、代碼質量的提高,更是一種思想的提高,現在都在講測試驅動開發、回歸質量大概就是這種思想。
JUnit使用起來不僅簡單方便,其源碼更是短小精悍,Erich Gamma 是著名的 GoF 之一,在JUnit中設計模式的使用堪稱經典,有優良的擴展性和可重用性。值得細細品味。
敬請關注將要發表的《JUnit源碼解析》系列博客(這需要提前了解Java的注解、反射等高級特性)。
@Copyright:http://www.cnblogs.com/wxisme/ 不當之處,歡迎交流。