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