1、幾個相關的概念
白盒測試——把測試對象看作一個打開的盒子,程序內部的邏輯結構和其他信息對測試人員是公開的。
回歸測試——軟件或環境的修復或更正後的“再測試”,自動測試工具對這類測試尤其有用。
單元測試——是最小粒度的測試,以測試某個功能或代碼塊。一般由程序員來做,因為它需要知道內部程序設計和編碼的細節。
JUnit ——是一個開發源代碼的Java測試框架,用於編寫和運行可重復的測試。他是用於單元測試框架體系xUnit的一個實例(用於java語言)。主要用於白盒測試,回歸測試。
2、單元測試概述
2.1、單元測試的好處
A、提高開發速度——測試是以自動化方式執行的,提升了測試代碼的執行效率。
B、提高軟件代碼質量——它使用小版本發布至集成,便於實現人員除錯。同時引入重構概念,讓代碼更干淨和富有彈性。
C、提升系統的可信賴度——它是回歸測試的一種。支持修復或更正後的“再測試”,可確保代碼的正確性。
2.2、單元測試的針對對象
A、面向過程的軟件開發針對過程。
B、面向對象的軟件開發針對對象。
C、可以做類測試,功能測試,接口測試(最常用於測試類中的方法)。
2.3、單元測試工具和框架
目前的最流行的單元測試工具是xUnit系列框架,常用的根據語言不同分為JUnit(java),CppUnit(C++),DUnit (Delphi ),NUnit(.net),PhpUnit(Php )等等。該測試框架的第一個和最傑出的應用就是由Erich Gamma (《設計模式》的作者)和Kent Beck(XP(Extreme Programming)的創始人 )提供的開放源代碼的JUnit。
3.Junit入門簡介
3.1、JUnit的好處和JUnit單元測試編寫原則
好處:
A、可以使測試代碼與產品代碼分開。
B、針對某一個類的測試代碼通過較少的改動便可以應用於另一個類的測試。
C、易於集成到測試人員的構建過程中,JUnit和Ant的結合可以實施增量開發。
D、JUnit是公開源代碼的,可以進行二次開發。
C、可以方便地對JUnit進行擴展。
編寫原則:
A、是簡化測試的編寫,這種簡化包括測試框架的學習和實際測試單元的編寫。
B、是使測試單元保持持久性。
C、是可以利用既有的測試來編寫相關的測試。
3.2、JUnit的特征
A、使用斷言方法判斷期望值和實際值差異,返回Boolean值。
B、測試驅動設備使用共同的初始化變量或者實例。
C、測試包結構便於組織和集成運行。
D、支持圖型交互模式和文本交互模式。
3.3、JUnit框架組成
A、對測試目標進行測試的方法與過程集合,可稱為測試用例(TestCase)。
B、測試用例的集合,可容納多個測試用例(TestCase),將其稱作測試包(TestSuite)。
C、測試結果的描述與記錄。(TestResult) 。
D、測試過程中的事件監聽者(TestListener)。
E、每一個測試方法所發生的與預期不一致狀況的描述,稱其測試失敗元素(TestFailure)
F、JUnit Framework中的出錯異常(AssertionFailedError)。
JUnit框架是一個典型的Composite模式:TestSuite可以容納任何派生自Test的對象;當調用TestSuite對象的run()方法是,會遍歷自己容納的對象,逐個調用它們的run()方法。(可參考《程序員》2003-6期)。
3.4、JUnit的安裝和配置
JUnit安裝步驟分解:
在http://download.sourceforge.net/junit/中下載JUnit包並將Junit壓縮包解壓到一個物理目錄中(例如C:\Junit3.8.1)。
記錄Junit.jar文件所在目錄名(例如C:\Junit3.8.1\Junit.jar)。
進入操作系統(以Windows2000操作系統為准),按照次序點擊“開始 設置 控制面板”。
在控制面板選項中選擇“系統”,點擊“環境變量”,在“系統變量”的“變量”列表框中選擇“CLASS-PATH”關鍵字(不區分大小寫),如果該關鍵字不存在則添加。
雙擊“CLASS-PATH”關鍵字添加字符串“C:\Junit3.8.1\Junti.jar”(注意,如果已有別的字符串請在該字符串的字符結尾加上分號“;”),這樣確定修改後Junit就可以在集成環境中應用了。
對於IDE環境,對於需要用到的JUnit的項目增加到lib中,其設置不同的IDE有不同的設置 。
3.5、JUnit中常用的接口和類
Test接口——運行測試和收集測試結果
Test接口使用了Composite設計模式,是單獨測試用例 (TestCase),聚合測試模式(TestSuite)及測試擴展(TestDecorator)的共同接口。
它的public int countTestCases()方法,它來統計這次測試有多少個TestCase,另外一個方法就是public void run( TestResult ),TestResult是實例接受測試結果, run方法執行本次測試。
TestCase抽象類——定義測試中固定方法
TestCase是Test接口的抽象實現,(不能被實例化,只能被繼承)其構造函數TestCase(string name)根據輸入的測試名稱name創建一個測試實例。由於每一個TestCase在創建時都要有一個名稱,若某測試失敗了,便可識別出是哪個測試失敗。
TestCase類中包含的setUp()、tearDown()方法。setUp()方法集中初始化測試所需的所有變量和實例,並且在依次調用測試類中的每個測試方法之前再次執行setUp()方法。tearDown()方法則是在每個測試方法之後,釋放測試程序方法中引用的變量和實例。
開發人員編寫測試用例時,只需繼承TestCase,來完成run方法即可,然後JUnit獲得測試用例,執行它的run方法,把測試結果記錄在TestResult之中。
Assert靜態類——一系列斷言方法的集合
Assert包含了一組靜態的測試方法,用於期望值和實際值比對是否正確,即測試失敗,Assert類就會拋出一個AssertionFailedError異常,JUnit測試框架將這種錯誤歸入Failes並加以記錄,同時標志為未通過測試。如果該類方法中指定一個String類型的傳參則該參數將被做為AssertionFailedError異常的標識信息,告訴測試人員改異常的詳細信息。
JUnit 提供了6大類31組斷言方法,包括基礎斷言、數字斷言、字符斷言、布爾斷言、對象斷言。
其中assertEquals(Object expcted,Object actual)內部邏輯判斷使用equals()方法,這表明斷言兩個實例的內部哈希值是否相等時,最好使用該方法對相應類實例的值進行比較。而assertSame(Object expected,Object actual)內部邏輯判斷使用了Java運算符“==”,這表明該斷言判斷兩個實例是否來自於同一個引用(Reference),最好使用該方法對不同類的實例的值進行比對。asserEquals(String message,String expected,String actual)該方法對兩個字符串進行邏輯比對,如果不匹配則顯示著兩個字符串有差異的地方。ComparisonFailure類提供兩個字符串的比對,不匹配則給出詳細的差異字符。
TestSuite測試包類——多個測試的組合
TestSuite類負責組裝多個Test Cases。待測得類中可能包括了對被測類的多個測試,而TestSuit負責收集這些測試,使我們可以在一個測試中,完成全部的對被測類的多個測試。
TestSuite類實現了Test接口,且可以包含其它的TestSuites。它可以處理加入Test時的所有拋出的異常。
TestSuite處理測試用例有6個規約(否則會被拒絕執行測試)
A 測試用例必須是公有類(Public)
B 測試用例必須繼承與TestCase類
C 測試用例的測試方法必須是公有的( Public )
D 測試用例的測試方法必須被聲明為Void
E 測試用例中測試方法的前置名詞必須是test
F 測試用例中測試方法誤任何傳遞參數
n TestResult結果類和其它類與接口
TestResult結果類集合了任意測試累加結果,通過TestResult實例傳遞個每個測試的Run()方法。TestResult在執行TestCase是如果失敗會異常拋出
TestListener接口是個事件監聽規約,可供TestRunner類使用。它通知listener的對象相關事件,方法包括測試開始startTest(Test test),測試結束endTest(Test test),錯誤,增加異常addError(Test test,Throwable t)和增加失敗addFailure(Test test,AssertionFailedError t)
TestFailure失敗類是個“失敗”狀況的收集類,解釋每次測試執行過程中出現的異常情況。其toString()方法返回“失敗”狀況的簡要描述
3.6、JUnit一個實例
在控制台中簡單的范例如下:
1、寫個待測試的Triangle類,創建一個TestCase的子類ExampleTest:
2、ExampleTest中寫一個或多個測試方法,斷言期望的結果(注意:以test作為待測試的方法的開頭,這樣這些方法可以被自動找到並被測試)
3、ExampleTest中寫一個suite()方法,它會使用反射動態的創建一個包含所有的testXxxx方法的測試套件:
4、ExampleTest可以寫setUp()、tearDown()方法,以便於在測試時初始化或銷毀測試所需的所有變量和實例。(不是必須的)
5、寫一個main()方法以文本運行器或其它GUI的方式方便的運行測試
6、編譯ExampleTest,執行測試。
3.7、Eclipse中JUnit的使用
Eclipse自帶了一個JUnit的插件,不用安裝就可以在你的項目中開始測試相關的類,並且可以調試你的測試用例和被測試類。
使用步驟如下:
1、新建一個測試用例,點擊“File->New->Other…菜單項,在彈出的“New”對話框中選擇”Java->JUnit”,下的TestCase 或TestSuite,就進入“New JUnit TestCase”對話框
2、在“New JUnit TestCase”對話框填寫相應的欄目,主要有Name(測試用例名),SuperClass(測試的超類一般是默認的junit.framework.TestCase),Class Under Test(被測試的類),Source Folder(測試用例保存的目錄),Package(測試用例包名),及是否自動生成main,setUp,tearDown方法。
3、如果點擊下面的”Next>”按鈕,你還可以直接勾選你想測試的被測試類的方法,Eclipse將自動生成與被選方法相應的測試方法,點擊“Fishish”按鈕後一個測試用例就創建好了。
4、編寫完成你的測試用例後,點擊“Run”按鈕就可以看到運行結果了。
3.8、JUnit的擴展應用
以下羅列了些JUnit的擴展應用:
JUnit + HttpUnit=WEB功能測試工具
JUnit + hansel =代碼覆蓋測試工具
JUnit + abbot =界面自動回放測試工具
JUnit + dbunit =數據庫測試工具
JUnit + junitperf=性能測試工具
3.9、一些使用JUnit經驗
不要用TestCase的構造函數初始化,而要用setUp()和tearDown()方法。
不要依賴或假定測試運行的順序,因為JUnit利用Vector保存測試方法。所以不同的平台會按不同的順序從Vector中取出測試方法。
避免編寫有副作用的TestCase。例如:如果隨後的測試依賴於某些特定的交易數據,就不要提交交易數據。簡單的回滾就可以了。
當繼承一個測試類時,記得調用父類的setUp()和tearDown()方法。
將測試代碼和工作代碼放在一起,一邊同步編譯和更新。
測試類和測試方法應該有一致的命名方案。如在工作類名前加上test從而形成測試類名。
確保測試與時間無關,不要依賴使用過期的數據進行測試。導致在隨後的維護過程中很難重現測試。
如果你編寫的軟件面向國際市場,編寫測試時要考慮國際化的因素。不要僅用母語的Locale進行測試。
盡可能地利用JUnit提供地assert/fail方法以及異常處理的方法,可以使代碼更為簡潔。
測試要盡可能地小,執行速度快。
參考資料與附件
1.http:// www.junit.org JUnit官方網站
2.http://bbs.51cmm.com 的測試論壇
3.http://www.uml.org.cn 的軟件測試專欄
4.單元測試 《程序員》 2002年7期
5.JUnit設計模式分析 《程序員》2003年6期
6.《軟件測試和JUnit實踐》
7.附件Triangle.java 一個要測試的類
8.附件ExampleTest.java 一個測試用例類
Triangle.java
/**
* this is Triangle class
* @author liujun
*/
public class Triangle {
//定義三角形的三邊
protected long lborderA = 0;
protected long lborderB = 0;
protected long lborderC = 0;
//構造函數
public Triangle(long lborderA,long lborderB,long lborderC){
this.lborderA = lborderA;
this.lborderB = lborderB;
this.lborderC = lborderC;
}
/**
* 判斷是否是三角形
* 是返回ture;不是返回false
*/
public boolean isTriangle(Triangle triangle){
boolean isTrue = false;
//判斷邊界,大於0小於200,出界返回false
if((triangle.lborderA>0&&triangle.lborderA<200)
&&(triangle.lborderB>0&&triangle.lborderB<200)
&&(triangle.lborderC>0&&triangle.lborderC<200)){
//判斷兩邊之和大於第三邊
if((triangle.lborderA<(triangle.lborderB+triangle.lborderC))
&&(triangle.lborderB<(triangle.lborderA+triangle.lborderC))
&&(triangle.lborderC<(triangle.lborderA+triangle.lborderB)))
isTrue = true;
}
return isTrue;
}
/*
* 判斷三角形類型
* 等腰三角形返回字符串“等腰三角形”;
* 等邊三角形返回字符串“等邊三角形”;
* 其它三角形返回字符串“不等邊三角形”;
*/
public String isType(Triangle triangle){
String strType = "";
// 判斷是否是三角形
if(this.isTriangle(triangle)){
//判斷是否是等邊三角形 if(triangle.lborderA==triangle.lborderB&
//&triangle.lborderB==triangle.lborderC)
strType = "等邊三角形";
//判斷是否是不等邊三角形
else if((triangle.lborderA!=triangle.lborderB)&&
(triangle.lborderB!=triangle.lborderC)&&
(triangle.lborderA!=triangle.lborderC))
strType = "不等邊三角形";
else
strType="等腰三角形";
}
return strType;
}
}
ExampleTest.java
import junit.framework.*;
/**
* Some tests.
*
*/
public class ExampleTest extends TestCase {
public Triangle triangle;
//初始化
protected void setUp() {
triangle=new Triangle(10,2,9);
}
public static Test suite() {
return new TestSuite(ExampleTest.class);
}
//函數isTriangle()的測試用例
public void testIsTriangle() {
assertTrue(triangle.isTriangle(triangle));
}
//函數isType()的測試用例
public void testIsType(){
assertEquals("這次測試",triangle.isType(triangle),"不等邊三角形");
}
//執行測試
public static void main (String[] args) {
//文本方式
junit.textui.TestRunner.run(suite());
//Swingui方式
//junit.swingui.TestRunner.run(suite().getClass());
//awtui方式
//junit.awtui.TestRunner.run(suite().getClass());
}
}