JUnit是一個(gè)Java語(yǔ)言的單元測(cè)試框架。它由Kent Beck和Erich Gamma建立,逐漸成為源于Kent Beck的sUnit的xUnit家族中最為成功的一個(gè)。JUnit有它自己的JUnit擴(kuò)展生態(tài)圈。多數(shù)Java的開發(fā)環(huán)境都已經(jīng)集成了JUnit作為單元測(cè)試的工具。
JUnit是由Erich Gamma和Kent Beck編寫的一個(gè)回歸測(cè)試框架(regression testing framework)。Junit測(cè)試是程序員測(cè)試,即所謂白盒測(cè)試,因?yàn)槌绦騿T知道被測(cè)試的軟件如何(How)完成功能和完成什么樣(What)的功能。Junit是一套框架,繼承TestCase類,就可以用Junit進(jìn)行自動(dòng)測(cè)試了。
安裝很簡(jiǎn)單,先到以下地址下載一個(gè)最新的zip包:
下載完以后解壓到你喜歡的目錄下,假設(shè)是JUNIT_HOME,然后將JUNIT_HOME下的junit.jar包加到你的系統(tǒng)的CLASSPATH環(huán)境變量中,對(duì)于IDE環(huán)境,對(duì)于需要用到的junit的項(xiàng)目增加到lib中,其設(shè)置不同的IDE有不同的設(shè)置,這里不多講。
最簡(jiǎn)單的范例如下:
1、創(chuàng)建一個(gè)TestCase的子類
package junitfaq;
import java.util.*;
import junit.framework.*;
public class SimpleTest extends TestCase {
public SimpleTest(String name) {
super(name);
}
2、寫一個(gè)測(cè)試方法斷言期望的結(jié)果
public void testEmptyCollection(){
Collection collection = new ArrayList();
assertTrue(collection.isEmpty());
}
注意:JUnit推薦的做法是以test作為待測(cè)試的方法的開頭,這樣這些方法可以被自動(dòng)找到并被測(cè)試。
3、寫一個(gè)suite()方法,它會(huì)使用反射動(dòng)態(tài)的創(chuàng)建一個(gè)包含所有的testXxxx方法的測(cè)試套件
public static Test suite() {
return new TestSuite(SimpleTest.class);
}
4、寫一個(gè)main()方法以文本運(yùn)行器的方式方便的運(yùn)行測(cè)試
public static void main(String args[]) {
junit.textui.TestRunner.run(suite());
}
}
5、運(yùn)行測(cè)試
以文本方式運(yùn)行:
java junitfaq.SimpleTest
通過的測(cè)試結(jié)果是:
.
Time: 0
OK (1 tests)
Time上的小點(diǎn)表示測(cè)試個(gè)數(shù),如果測(cè)試通過則顯示OK。否則在小點(diǎn)的后邊標(biāo)上Fail,表示該測(cè)試失敗。
每次的測(cè)試結(jié)果都應(yīng)該是OK的,這樣才能說(shuō)明測(cè)試是成功的,如果不成功就要馬上根據(jù)提示信息進(jìn)行修正了。
如果JUnit報(bào)告了測(cè)試沒有成功,它會(huì)區(qū)分失敗(failures)和錯(cuò)誤(errors)。失敗是你的代碼中的assert方法失敗引起的;而錯(cuò)誤則是代碼異常引起的,例如ArrayIndexOutOfBoundsException。
以圖形方式運(yùn)行:
java junit.swingui.TestRunner junitfaq.SimpleTest
通過的測(cè)試結(jié)果在圖形界面的綠色條部分。
以上是最簡(jiǎn)單的測(cè)試樣例,在實(shí)際的測(cè)試中我們測(cè)試某個(gè)類的功能是常常需要執(zhí)行一些共同的操作,完成以后需要銷毀所占用的資源(例如網(wǎng)絡(luò)連接、數(shù)據(jù)庫(kù)連接,關(guān)閉打開的文件等),TestCase類給我們提供了setUp方法和tearDown方法,setUp方法的內(nèi)容在測(cè)試你編寫的TestCase子類的每個(gè)testXxxx方法之前都會(huì)運(yùn)行,而tearDown方法的內(nèi)容在每個(gè)testXxxx方法結(jié)束以后都會(huì)執(zhí)行。這個(gè)既共享了初始化代碼,又消除了各個(gè)測(cè)試代碼之間可能產(chǎn)生的相互影響。
不要認(rèn)為壓力大,就不寫測(cè)試代碼。相反編寫測(cè)試代碼會(huì)使你的壓力逐漸減輕,因?yàn)橥ㄟ^編寫測(cè)試代碼,你對(duì)類的行為有了確切的認(rèn)識(shí)。你會(huì)更快地編寫出有效率地工作代碼。
下面是一些具體的編寫測(cè)試代碼的技巧或較好的實(shí)踐方法:
1.不要用TestCase的構(gòu)造函數(shù)初始化Fixture,而要用setUp()和tearDown()方法。
2.不要依賴或假定測(cè)試運(yùn)行的順序,因?yàn)镴Unit利用Vector保存測(cè)試方法。所以不同的平臺(tái)會(huì)按不同的順序從Vector中取出測(cè)試方法。
3.避免編寫有副作用的TestCase。例如:如果隨后的測(cè)試依賴于某些特定的交易數(shù)據(jù),就不要提交交易數(shù)據(jù)。簡(jiǎn)單的回滾就可以了。
4.當(dāng)繼承一個(gè)測(cè)試類時(shí),記得調(diào)用父類的setUp()和tearDown()方法。
5.將測(cè)試代碼和工作代碼放在一起,一邊同步編譯和更新。(使用Ant中有支持junit的task.)
6.測(cè)試類和測(cè)試方法應(yīng)該有一致的命名方案。如在工作類名前加上test從而形成測(cè)試類名。
7.確保測(cè)試與時(shí)間無(wú)關(guān),不要依賴使用過期的數(shù)據(jù)進(jìn)行測(cè)試。導(dǎo)致在隨后的維護(hù)過程中很難重現(xiàn)測(cè)試。
8.如果你編寫的軟件面向國(guó)際市場(chǎng),編寫測(cè)試時(shí)要考慮國(guó)際化的因素。不要僅用母語(yǔ)的Locale進(jìn)行測(cè)試。
9.盡可能地利用JUnit提供地assert/fail方法以及異常處理的方法,可以使代碼更為簡(jiǎn)潔。
10.測(cè)試要盡可能地小,執(zhí)行速度快。
11.不要硬性規(guī)定數(shù)據(jù)文件的路徑。
12.利用Junit的自動(dòng)異常處理書寫簡(jiǎn)潔的測(cè)試代碼
事實(shí)上在Junit中使用try-catch來(lái)捕獲異常是沒有必要的,Junit會(huì)自動(dòng)捕獲異常。那些沒有被捕獲的異常就被當(dāng)成錯(cuò)誤處理。
13.充分利用Junit 的assert/fail方法
assertSame()用來(lái)測(cè)試兩個(gè)引用是否指向同一個(gè)對(duì)象
assertEquals()用來(lái)測(cè)試兩個(gè)對(duì)象是否相等
14.確保測(cè)試代碼與時(shí)間無(wú)關(guān)
15.使用文檔生成器做測(cè)試文檔。
JUnit和ant結(jié)合
ant提供了兩個(gè)target:junit和junitreport運(yùn)行所有測(cè)試用例,并生成html格式的報(bào)表
具體操作如下:
1.將 junit.jar 放在 ANT_HOMElib 目錄下
2.修改 build.xml,加入如下 內(nèi)容:
-------------- One or more tests failed, check the report for detail... -----------------------------
運(yùn)行這個(gè)target,ant會(huì)運(yùn)行每個(gè)TestCase,在report目錄下就有了很多TEST*.xml和一些網(wǎng)頁(yè)打開report目錄下的 index.html就可以看到很直觀的測(cè)試運(yùn)行報(bào)告,一目了然。
在Eclipse中開發(fā)、運(yùn)行JUnit測(cè)試相當(dāng)簡(jiǎn)單。因?yàn)镋clipse本身集成了JUnit相關(guān)組件,并對(duì)JUnit的運(yùn)行提供了無(wú)縫的支持。
junit3.x
我們通常使用junit 3.8
(1)、使用junit3.x版本進(jìn)行單元測(cè)試時(shí),測(cè)試類必須要繼承于TestCase父類;
(2)、測(cè)試方法需要遵循的原則:
A、public的
B、void的
C、無(wú)方法參數(shù)
D、方法名稱必須以test開頭
(3)、不同的Test Case之間一定要保持完全的獨(dú)立性,不能有任何的關(guān)聯(lián)。
(4)、我們要掌握好測(cè)試方法的順序,不能依賴于測(cè)試方法自己的執(zhí)行順序。
demo:
public class TestMyNumber extends TestCase {
private MyNumber myNumber;
public TestMyNumber(String name) {
super(name);
}
// 在每個(gè)測(cè)試方法執(zhí)行 [之前] 都會(huì)被調(diào)用
@Override
public void setUp() throws Exception {
// System.out.println("歡迎使用Junit進(jìn)行單元測(cè)試…");
myNumber = new MyNumber();
}
// 在每個(gè)測(cè)試方法執(zhí)行 [之后] 都會(huì)被調(diào)用
@Override
public void tearDown() throws Exception {
// System.out.println("Junit單元測(cè)試結(jié)束…");
}
public void testDivideByZero() {
Throwable te = null;
try {
myNumber.divide(6, 0);
Assert.fail("測(cè)試失敗");
} catch (Exception e) {
e.printStackTrace();
te = e;
}
Assert.assertEquals(Exception.class, te.getClass());
Assert.assertEquals("除數(shù)不能為 0 ", te.getMessage());
}
}
junit4.x
(1)、使用junit4.x版本進(jìn)行單元測(cè)試時(shí),不用測(cè)試類繼承TestCase父類,因?yàn)?,junit4.x全面引入了Annotation來(lái)執(zhí)行我們編寫的測(cè)試。
(2)、junit4.x版本,引用了注解的方式,進(jìn)行單元測(cè)試;
(3)、junit4.x版本我們常用的注解:
A、@Before 注解:與junit3.x中的setUp()方法功能一樣,在每個(gè)測(cè)試方法之前執(zhí)行;
B、@After 注解:與junit3.x中的tearDown()方法功能一樣,在每個(gè)測(cè)試方法之后執(zhí)行;
C、@BeforeClass 注解:在所有方法執(zhí)行之前執(zhí)行;
D、@AfterClass 注解:在所有方法執(zhí)行之后執(zhí)行;
E、@Test(timeout=xxx)注解:設(shè)置當(dāng)前測(cè)試方法在一定時(shí)間內(nèi)運(yùn)行完,否則返回錯(cuò)誤;
F、@Test(expected=Exception.class)注解:設(shè)置被測(cè)試的方法是否有異常拋出。拋出異常類型為:Exception.class;
G、@Ignore注解:注釋掉一個(gè)測(cè)試方法或一個(gè)類,被注釋的方法或類,不會(huì)被執(zhí)行。
demo:
package com.an.junit;
import static org.junit.Assert.*;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class TestMyNumber {
private MyNumber myNumber;
@BeforeClass
// 在所有方法執(zhí)行之前執(zhí)行
public static void globalInit() {
System.out.println("init all method...");
}
@AfterClass
// 在所有方法執(zhí)行之后執(zhí)行
public static void globalDestory() {
System.out.println("destory all method...");
}
@Before
// 在每個(gè)測(cè)試方法之前執(zhí)行
public void setUp() {
System.out.println("start setUp method");
myNumber = new MyNumber();
}
@After
// 在每個(gè)測(cè)試方法之后執(zhí)行
public void tearDown() {
System.out.println("end tearDown method");
}
@Test(timeout=600)// 設(shè)置限定測(cè)試方法的運(yùn)行時(shí)間 如果超出則返回錯(cuò)誤
public void testAdd() {
System.out.println("testAdd method");
int result = myNumber.add(2, 3);
assertEquals(5, result);
}
@Test
public void testSubtract() {
System.out.println("testSubtract method");
int result = myNumber.subtract(1, 2);
assertEquals(-1, result);
}
@Test
public void testMultiply() {
System.out.println("testMultiply method");
int result = myNumber.multiply(2, 3);
assertEquals(6, result);
}
@Test
public void testDivide() {
System.out.println("testDivide method");
int result = 0;
try {
result = myNumber.divide(6, 2);
} catch (Exception e) {
fail();
}
assertEquals(3, result);
}
@Test(expected = Exception.class)
public void testDivide2() throws Exception {
System.out.println("testDivide2 method");
myNumber.divide(6, 0);
fail("test Error");
}
public static void main(String[] args) {
}
}
另外junit是在極限編程和重構(gòu)(refactor)中被極力推薦使用的工具,因?yàn)樵趯?shí)現(xiàn)自動(dòng)單元測(cè)試的情況下可以大大的提高開發(fā)的效率,但是實(shí)際上編寫測(cè)試代碼也是需要耗費(fèi)很多的時(shí)間和精力的,那么使用這個(gè)東西好處到底在哪里呢?筆者認(rèn)為是這樣的:
極限編程
要求在編寫代碼之前先寫測(cè)試,這樣可以強(qiáng)制你在寫代碼之前好好的思考代碼(方法)的功能和邏輯,否則編寫的代碼很不穩(wěn)定,那么你需要同時(shí)維護(hù)測(cè)試代碼和實(shí)際代碼,這個(gè)工作量就會(huì)大大增加。因此在極限編程中,基本過程是這樣的:構(gòu)思-> 編寫測(cè)試代碼-> 編寫代碼-> 測(cè)試,而且編寫測(cè)試和編寫代碼都是增量式的,寫一點(diǎn)測(cè)一點(diǎn),在編寫以后的代碼中如果發(fā)現(xiàn)問題可以較快的追蹤到問題的原因,減小回歸錯(cuò)誤的糾錯(cuò)難度。
重構(gòu)
其好處和極限編程中是類似的,因?yàn)橹貥?gòu)也是要求改一點(diǎn)測(cè)一點(diǎn),減少回歸錯(cuò)誤造成的時(shí)間消耗。
其他情況
我們?cè)陂_發(fā)的時(shí)候使用junit寫一些適當(dāng)?shù)臏y(cè)試也是有必要的,因?yàn)橐话阄覀円彩切枰帉憸y(cè)試的代碼的,可能原來(lái)不是使用的junit,如果使用junit,而且針對(duì)接口(方法)編寫測(cè)試代碼會(huì)減少以后的維護(hù)工作,例如以后對(duì)方法內(nèi)部的修改(這個(gè)就是相當(dāng)于重構(gòu)的工作了)。另外就是因?yàn)閖unit有斷言功能,如果測(cè)試結(jié)果不通過會(huì)告訴我們哪個(gè)測(cè)試不通過,為什么,而如果是像以前的一般做法是寫一些測(cè)試代碼看其輸出結(jié)果,然后再由自己來(lái)判斷結(jié)果是否正確,使用junit的好處就是這個(gè)結(jié)果是否正確的判斷是它來(lái)完成的,我們只需要看看它告訴我們結(jié)果是否正確就可以了,在一般情況下會(huì)大大提高效率。
JUnit是一個(gè)開放源代碼的Java測(cè)試框架,用于編寫和運(yùn)行可重復(fù)的測(cè)試。他是用于單元測(cè)試框架體系xUnit的一個(gè)實(shí)例(用于java語(yǔ)言)。它包括以下特性:
1、用于測(cè)試期望結(jié)果的斷言(Assertion)
2、用于共享共同測(cè)試數(shù)據(jù)的測(cè)試工具
3、用于方便的組織和運(yùn)行測(cè)試的測(cè)試套件
4、圖形和文本的測(cè)試運(yùn)行器