JUnit是一個Java語言的單元測試(shi)(shi)框(kuang)架。它(ta)由Kent Beck和(he)Erich Gamma建立(li),逐(zhu)漸成(cheng)為源于(yu)Kent Beck的sUnit的xUnit家族中最為成(cheng)功的一個。JUnit有它(ta)自己的JUnit擴(kuo)展生態(tai)圈。多數Java的開發環境都已經(jing)集(ji)成(cheng)了JUnit作(zuo)為單元測試(shi)(shi)的工具。
JUnit是(shi)(shi)由Erich Gamma和Kent Beck編寫的一個回歸測(ce)(ce)試(shi)框架(jia)(regression testing framework)。Junit測(ce)(ce)試(shi)是(shi)(shi)程(cheng)序(xu)員(yuan)(yuan)測(ce)(ce)試(shi),即所謂(wei)白盒測(ce)(ce)試(shi),因為程(cheng)序(xu)員(yuan)(yuan)知道(dao)被測(ce)(ce)試(shi)的軟件如何(he)(How)完成功能(neng)和完成什么樣(What)的功能(neng)。Junit是(shi)(shi)一套框架(jia),繼(ji)承TestCase類,就可(ke)以用Junit進行自動測(ce)(ce)試(shi)了。
安裝很(hen)簡單,先到以下(xia)地址下(xia)載一個(ge)最新的zip包:
下(xia)載完以后解壓到你喜歡(huan)的(de)(de)(de)目(mu)錄下(xia),假設(she)是JUNIT_HOME,然后將JUNIT_HOME下(xia)的(de)(de)(de)junit.jar包加到你的(de)(de)(de)系(xi)統的(de)(de)(de)CLASSPATH環境(jing)(jing)變量中(zhong),對(dui)于IDE環境(jing)(jing),對(dui)于需(xu)要用到的(de)(de)(de)junit的(de)(de)(de)項目(mu)增加到lib中(zhong),其設(she)置不同的(de)(de)(de)IDE有不同的(de)(de)(de)設(she)置,這里不多講。
最簡單的范例如下:
1、創(chuang)建一個TestCase的子類
package junitfaq;
import java.util.*;
import junit.framework.*;
public class SimpleTest extends TestCase {
public SimpleTest(String name) {
super(name);
}
2、寫一個(ge)測試方(fang)法斷言期(qi)望的結果
public void testEmptyCollection(){
Collection collection = new ArrayList();
assertTrue(collection.isEmpty());
}
注意:JUnit推薦的(de)做法是以(yi)test作為待測(ce)試的(de)方法的(de)開頭(tou),這樣這些方法可以(yi)被(bei)自動找到并(bing)被(bei)測(ce)試。
3、寫(xie)一(yi)個suite()方(fang)法,它會(hui)使(shi)用反射動態的創建一(yi)個包(bao)含(han)所有(you)的testXxxx方(fang)法的測(ce)試(shi)套件
public static Test suite() {
return new TestSuite(SimpleTest.class);
}
4、寫一個(ge)main()方(fang)(fang)法以文本運(yun)行器(qi)的方(fang)(fang)式方(fang)(fang)便的運(yun)行測試
public static void main(String args[]) {
junit.textui.TestRunner.run(suite());
}
}
5、運行測試
以文本方式運行:
java junitfaq.SimpleTest
通過的測試結果是:
.
Time: 0
OK (1 tests)
Time上(shang)(shang)的小點(dian)表示測試(shi)個數,如(ru)果測試(shi)通過則顯(xian)示OK。否則在(zai)小點(dian)的后邊標上(shang)(shang)Fail,表示該測試(shi)失敗。
每(mei)次的測(ce)試(shi)結(jie)果都應該是OK的,這樣才能(neng)說明測(ce)試(shi)是成功(gong)的,如果不成功(gong)就要(yao)馬上根據提(ti)示信息進行修正(zheng)了。
如(ru)果JUnit報告了測試(shi)沒有成(cheng)功,它會區分失敗(failures)和錯(cuo)(cuo)誤(errors)。失敗是你的(de)(de)代碼中的(de)(de)assert方法失敗引起的(de)(de);而(er)錯(cuo)(cuo)誤則(ze)是代碼異常引起的(de)(de),例如(ru)ArrayIndexOutOfBoundsException。
以圖形方式運行:
java junit.swingui.TestRunner junitfaq.SimpleTest
通(tong)過的(de)測試(shi)結果(guo)在圖形(xing)界(jie)面的(de)綠色條部分。
以上(shang)是最簡單(dan)的測(ce)試樣(yang)例,在實(shi)際的測(ce)試中我們測(ce)試某個(ge)(ge)類的功能(neng)是常(chang)常(chang)需(xu)要執行一(yi)些共(gong)同的操作,完成(cheng)以后(hou)需(xu)要銷毀(hui)所占(zhan)用的資源(yuan)(例如網絡連(lian)接、數(shu)據庫(ku)連(lian)接,關閉打開的文件等),TestCase類給我們提供了setUp方法(fa)和tearDown方法(fa),setUp方法(fa)的內容在測(ce)試你編寫的TestCase子類的每個(ge)(ge)testXxxx方法(fa)之(zhi)前都(dou)會運行,而(er)tearDown方法(fa)的內容在每個(ge)(ge)testXxxx方法(fa)結束以后(hou)都(dou)會執行。這個(ge)(ge)既(ji)共(gong)享(xiang)了初始化(hua)代碼,又消除了各個(ge)(ge)測(ce)試代碼之(zhi)間可能(neng)產生的相互影響。
不要認為(wei)壓力大,就(jiu)不寫測試(shi)代(dai)碼(ma)。相反編(bian)寫測試(shi)代(dai)碼(ma)會(hui)使你(ni)的(de)壓力逐(zhu)漸減輕,因為(wei)通過編(bian)寫測試(shi)代(dai)碼(ma),你(ni)對(dui)類的(de)行為(wei)有了確切的(de)認識。你(ni)會(hui)更快(kuai)地編(bian)寫出有效率地工作代(dai)碼(ma)。
下面(mian)是一些(xie)具體的編(bian)寫測試代(dai)碼的技(ji)巧或較好的實踐方法:
1.不要(yao)用TestCase的(de)構(gou)造函數初始(shi)化Fixture,而要(yao)用setUp()和tearDown()方法(fa)。
2.不要依賴或假定測(ce)試運行的順(shun)序(xu),因為JUnit利用(yong)Vector保存測(ce)試方法。所以不同(tong)的平(ping)臺會按(an)不同(tong)的順(shun)序(xu)從(cong)Vector中取出測(ce)試方法。
3.避免(mian)編寫有副(fu)作用(yong)的TestCase。例(li)如:如果隨后的測試(shi)依賴于某些特定的交(jiao)易(yi)數據,就(jiu)(jiu)不要(yao)提交(jiao)交(jiao)易(yi)數據。簡單(dan)的回滾就(jiu)(jiu)可以了。
4.當繼(ji)承(cheng)一個測試類時,記得調用(yong)父(fu)類的setUp()和(he)tearDown()方法。
5.將測試代碼(ma)和工作代碼(ma)放在一(yi)起,一(yi)邊同步編譯(yi)和更新。(使用Ant中有支持junit的task.)
6.測試(shi)類和測試(shi)方法應該有一致(zhi)的命名方案。如在工作(zuo)類名前加上(shang)test從而(er)形成測試(shi)類名。
7.確保測試與(yu)時間(jian)無關,不(bu)要依賴使用過期的數據進(jin)行測試。導(dao)致在隨后(hou)的維護過程中很難重(zhong)現測試。
8.如(ru)果你編寫的軟件面向國(guo)際市場,編寫測試時要考慮國(guo)際化(hua)的因素。不要僅用母語的Locale進行測試。
9.盡(jin)可(ke)能地(di)利用JUnit提(ti)供地(di)assert/fail方法(fa)以(yi)(yi)及異常處理的方法(fa),可(ke)以(yi)(yi)使代碼更為簡(jian)潔。
10.測試(shi)要盡可(ke)能(neng)地小,執行速度快。
11.不要硬性規定數據文件的路(lu)徑。
12.利(li)用(yong)Junit的自動異常(chang)處理(li)書寫簡潔的測(ce)試代碼
事實(shi)上在(zai)Junit中使用try-catch來(lai)捕獲(huo)異(yi)常(chang)是沒有必要的,Junit會自(zi)動捕獲(huo)異(yi)常(chang)。那(nei)些沒有被捕獲(huo)的異(yi)常(chang)就(jiu)被當(dang)成錯(cuo)誤處理。
13.充(chong)分(fen)利用Junit 的assert/fail方法(fa)
assertSame()用來測試兩(liang)個(ge)(ge)引用是否指向同一(yi)個(ge)(ge)對象
assertEquals()用來測試兩個對象是否相等
14.確保測(ce)試代碼與(yu)時間(jian)無關
15.使用文檔生成器做測試文檔。
JUnit和ant結合
ant提供(gong)了兩個target:junit和junitreport運行所有(you)測(ce)試(shi)用例(li),并生成html格(ge)式(shi)的報表(biao)
具體操作如下:
1.將 junit.jar 放在 ANT_HOMElib 目錄下
2.修改 build.xml,加入如下 內容:
-------------- One or more tests failed, check the report for detail... -----------------------------
運行(xing)這(zhe)個(ge)target,ant會(hui)運行(xing)每(mei)個(ge)TestCase,在report目(mu)錄下(xia)就有了很多TEST*.xml和(he)一些網頁打(da)開report目(mu)錄下(xia)的(de) index.html就可(ke)以看到很直觀(guan)的(de)測試運行(xing)報告,一目(mu)了然。
在(zai)Eclipse中開發、運行JUnit測(ce)試(shi)相(xiang)當(dang)簡單。因為Eclipse本身集成了(le)JUnit相(xiang)關組件,并對JUnit的(de)運行提供了(le)無縫(feng)的(de)支持。
junit3.x
我們通常(chang)使用(yong)junit 3.8
(1)、使用(yong)junit3.x版本進(jin)行單元(yuan)測試時,測試類必須要(yao)繼承(cheng)于TestCase父類;
(2)、測試方(fang)法需要遵循的原則(ze):
A、public的
B、void的
C、無方法參數
D、方法名稱必(bi)須(xu)以(yi)test開頭
(3)、不(bu)(bu)同的Test Case之間(jian)一定要保持完全的獨立(li)性(xing),不(bu)(bu)能有任何的關(guan)聯。
(4)、我們要掌握好(hao)測試(shi)方法的順序,不能依賴于(yu)測試(shi)方法自己的執行順序。
demo:
public class TestMyNumber extends TestCase {
private MyNumber myNumber;
public TestMyNumber(String name) {
super(name);
}
// 在(zai)每(mei)個(ge)測(ce)試方法(fa)執行 [之(zhi)前] 都會被調(diao)用
@Override
public void setUp() throws Exception {
// System.out.println("歡迎使用(yong)Junit進行單元測試…");
myNumber = new MyNumber();
}
// 在每個測試方(fang)法執行 [之后] 都會被調用
@Override
public void tearDown() throws Exception {
// System.out.println("Junit單元測試結束(shu)…");
}
public void testDivideByZero() {
Throwable te = null;
try {
myNumber.divide(6, 0);
Assert.fail("測試失(shi)敗");
} catch (Exception e) {
e.printStackTrace();
te = e;
}
Assert.assertEquals(Exception.class, te.getClass());
Assert.assertEquals("除數不(bu)能為 0 ", te.getMessage());
}
}
junit4.x
(1)、使用junit4.x版本(ben)進行單元測試時,不用測試類繼承TestCase父類,因為,junit4.x全面引入了Annotation來執行我們編寫的測試。
(2)、junit4.x版本,引用了注解(jie)的(de)方式,進行(xing)單元測試;
(3)、junit4.x版本我們(men)常(chang)用的(de)注解(jie):
A、@Before 注(zhu)解(jie):與(yu)junit3.x中的setUp()方法功能一樣,在每個(ge)測試方法之(zhi)前執行;
B、@After 注解:與junit3.x中的tearDown()方法功能(neng)一樣,在每個測(ce)試(shi)方法之后執行;
C、@BeforeClass 注解:在所有方法執行(xing)之前執行(xing);
D、@AfterClass 注(zhu)解(jie):在所有(you)方法(fa)執行(xing)之后執行(xing);
E、@Test(timeout=xxx)注解:設置當前測試方法在(zai)一定時(shi)間內運行完,否則返回錯誤;
F、@Test(expected=Exception.class)注解:設置(zhi)被測(ce)試的方法(fa)是否有異常(chang)拋(pao)出(chu)。拋(pao)出(chu)異常(chang)類型為:Exception.class;
G、@Ignore注(zhu)解:注(zhu)釋掉(diao)一個測試方法(fa)或(huo)一個類,被注(zhu)釋的方法(fa)或(huo)類,不會被執行。
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
// 在所有方法執(zhi)行之前執(zhi)行
public static void globalInit() {
System.out.println("init all method...");
}
@AfterClass
// 在所有方法執行之后(hou)執行
public static void globalDestory() {
System.out.println("destory all method...");
}
@Before
// 在每個(ge)測試方法之前執行
public void setUp() {
System.out.println("start setUp method");
myNumber = new MyNumber();
}
@After
// 在每(mei)個(ge)測試(shi)方法之后(hou)執(zhi)行
public void tearDown() {
System.out.println("end tearDown method");
}
@Test(timeout=600)// 設置(zhi)限定測(ce)試方(fang)法的運行(xing)時間 如果超(chao)出則(ze)返回錯誤
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) {
}
}
另外(wai)junit是在極(ji)(ji)限編(bian)程和(he)重構(gou)(refactor)中被極(ji)(ji)力(li)推(tui)薦使用的工具,因為在實現自動單元測試的情況下可以大大的提高開發的效率(lv),但(dan)是實際(ji)上編(bian)寫(xie)測試代碼也是需(xu)要耗(hao)費很多的時間和(he)精力(li)的,那么使用這個東西(xi)好處到底在哪里(li)呢?筆者認為是這樣的:
極限編程
要(yao)(yao)求在(zai)(zai)(zai)編(bian)(bian)(bian)寫代(dai)(dai)(dai)碼(ma)(ma)(ma)(ma)之前先(xian)寫測(ce)(ce)試,這(zhe)樣可以強制你(ni)在(zai)(zai)(zai)寫代(dai)(dai)(dai)碼(ma)(ma)(ma)(ma)之前好好的(de)(de)思考(kao)代(dai)(dai)(dai)碼(ma)(ma)(ma)(ma)(方法(fa))的(de)(de)功能和邏輯,否則編(bian)(bian)(bian)寫的(de)(de)代(dai)(dai)(dai)碼(ma)(ma)(ma)(ma)很不穩定,那么你(ni)需要(yao)(yao)同時維護測(ce)(ce)試代(dai)(dai)(dai)碼(ma)(ma)(ma)(ma)和實際代(dai)(dai)(dai)碼(ma)(ma)(ma)(ma),這(zhe)個工作量(liang)就(jiu)會大大增加。因此(ci)在(zai)(zai)(zai)極限編(bian)(bian)(bian)程(cheng)中,基本過程(cheng)是(shi)這(zhe)樣的(de)(de):構思-> 編(bian)(bian)(bian)寫測(ce)(ce)試代(dai)(dai)(dai)碼(ma)(ma)(ma)(ma)-> 編(bian)(bian)(bian)寫代(dai)(dai)(dai)碼(ma)(ma)(ma)(ma)-> 測(ce)(ce)試,而且(qie)編(bian)(bian)(bian)寫測(ce)(ce)試和編(bian)(bian)(bian)寫代(dai)(dai)(dai)碼(ma)(ma)(ma)(ma)都是(shi)增量(liang)式的(de)(de),寫一點測(ce)(ce)一點,在(zai)(zai)(zai)編(bian)(bian)(bian)寫以后的(de)(de)代(dai)(dai)(dai)碼(ma)(ma)(ma)(ma)中如果發現問題可以較快的(de)(de)追(zhui)蹤(zong)到問題的(de)(de)原因,減小回(hui)歸錯(cuo)誤的(de)(de)糾錯(cuo)難度。
重構
其好處和(he)極限編程中是類似的(de),因為重構也是要求改一點測(ce)一點,減少回(hui)歸錯誤造成的(de)時間消耗。
其他情況
我(wo)們在開發的(de)時候使(shi)(shi)用(yong)junit寫一些適當的(de)測(ce)試(shi)也(ye)是有必(bi)要的(de),因為(wei)一般(ban)我(wo)們也(ye)是需(xu)(xu)要編寫測(ce)試(shi)的(de)代碼(ma)的(de),可(ke)能(neng)原來不是使(shi)(shi)用(yong)的(de)junit,如果(guo)使(shi)(shi)用(yong)junit,而且針(zhen)對(dui)接口(方(fang)法(fa))編寫測(ce)試(shi)代碼(ma)會(hui)減少(shao)以后的(de)維護工(gong)作,例如以后對(dui)方(fang)法(fa)內(nei)部的(de)修(xiu)改(這(zhe)個(ge)(ge)就是相當于(yu)重構的(de)工(gong)作了)。另(ling)外就是因為(wei)junit有斷言功能(neng),如果(guo)測(ce)試(shi)結(jie)果(guo)不通過(guo)會(hui)告(gao)訴(su)我(wo)們哪個(ge)(ge)測(ce)試(shi)不通過(guo),為(wei)什么(me),而如果(guo)是像以前(qian)的(de)一般(ban)做法(fa)是寫一些測(ce)試(shi)代碼(ma)看(kan)其輸出結(jie)果(guo),然后再由自己來判斷結(jie)果(guo)是否正確(que),使(shi)(shi)用(yong)junit的(de)好處就是這(zhe)個(ge)(ge)結(jie)果(guo)是否正確(que)的(de)判斷是它(ta)來完成的(de),我(wo)們只需(xu)(xu)要看(kan)看(kan)它(ta)告(gao)訴(su)我(wo)們結(jie)果(guo)是否正確(que)就可(ke)以了,在一般(ban)情(qing)況下會(hui)大大提高效(xiao)率。
JUnit是(shi)一個開放源代碼(ma)的(de)(de)Java測(ce)試(shi)(shi)框(kuang)架(jia),用于(yu)編寫(xie)和運行(xing)可重復的(de)(de)測(ce)試(shi)(shi)。他是(shi)用于(yu)單元測(ce)試(shi)(shi)框(kuang)架(jia)體系xUnit的(de)(de)一個實例(用于(yu)java語言)。它包(bao)括以下(xia)特性(xing):
1、用于測(ce)試期望結果的斷言(Assertion)
2、用于共(gong)享共(gong)同測(ce)試(shi)數據的測(ce)試(shi)工具
3、用于方便的組織和(he)運行測試(shi)的測試(shi)套(tao)件
4、圖形(xing)和文本的測(ce)試運行(xing)器