您的位置:軟件測試 > 開源軟件測試 > 開源單元測試工具 > junit
怎樣使用Junit Framework進行單元測試的編寫
作者:網(wǎng)絡(luò)轉(zhuǎn)載 發(fā)布時間:[ 2013/5/7 11:30:54 ] 推薦標簽:

隨著Refactoring技術(shù)和XP軟件工程技術(shù)的廣泛推廣,單元測試的作用在軟件工程中變得越來越重要,而一個簡明易學(xué)、適用廣泛、高效穩(wěn)定的單元測試框架則對成功的實施單元測試有著至關(guān)重要的作用。在java編程語句環(huán)境里,Junit Framework是一個已經(jīng)被多數(shù)java程序員采用和實證的的測試框架,但是多數(shù)沒有嘗試Junit Framework的程序員在學(xué)習(xí)如何Junit Framework來編寫適應(yīng)自己開發(fā)項目的單元測試時,依然覺得有一定的難度,這可能是因為Junit隨框架代碼和實用工具附帶的用戶指南和文檔的著重點在于解釋單元測試框架的設(shè)計方法以及簡單的類使用說明,而對在特定的測試框架(Junit)下如何實施單元測試,如何在項目開發(fā)的過程中更新和維護已經(jīng)存在的單元測試代碼沒有詳細的解釋。因此本文檔兩個著重點對Junit所附帶的文檔進行進一步的補充和說明,使Junit能被更多的開發(fā)團隊采用,讓單元測試乃至Refactoring、XP技術(shù)更好在更多的開發(fā)團隊中推廣。

1. 單元測試的編寫原則

Junit附帶文檔所列舉的單元測試帶有一定的迷惑性,因為幾乎所有的示例單元都是針對某個對象的某個方法,似乎Junit的單元測試僅適用于類組織結(jié)構(gòu)的靜態(tài)約束,從而使初學(xué)者懷疑Junit下的單元測試所能帶來的效果。因此我們需要重新定義如何確定有價值的單元測試以及如何編寫這些單元測試、維護這些單元測試,從而讓更多的程序員接受和熟悉Junit下的單元測試的編寫。

在Junit單元測試框架的設(shè)計時,作者一共設(shè)定了三個總體目標,第一個是簡化測試的編寫,這種簡化包括測試框架的學(xué)習(xí)和實際測試單元的編寫;第二個是使測試單元保持持久性;第三個則是可以利用既有的測試來編寫相關(guān)的測試。從這三個目標可以看出,單元測試框架的基本設(shè)計考慮依然是從我們現(xiàn)有的測試方式和方法出發(fā),而只是使測試變得更加容易實施和擴展并保持持久性。因此編寫單元測試的原則可以從我們通常使用的測試方法借鑒和利用。

2. 如何確定單元測試

在我們通常的測試中,一個單元測試一般針對于特定對象的一個特定特性,譬如,假定我們編寫了一個針對特定數(shù)據(jù)庫訪問的連接池的類包實現(xiàn),我們會建立以下的單元測試:

在連接池啟動后,是否根據(jù)定義的規(guī)則在池中建立了相應(yīng)數(shù)量的數(shù)據(jù)庫連接
申請一個數(shù)據(jù)庫連接,是否根據(jù)定義的規(guī)則從池中直接獲得緩存連接的引用,還是建立新的連接
釋放一個數(shù)據(jù)庫連接后,連接是否根據(jù)定義的規(guī)則被池釋放或者緩存以便以后使用
后臺Housekeeping線程是否按照定義的規(guī)則釋放已經(jīng)過期的連接申請
如果連接有時間期限,后臺Housekeeping線程是否定期釋放已經(jīng)過期的緩存連接
這兒只列出了部分的可能測試,但是從這個列表我們可以看出單元測試的粒度。一個單元測試基本是以一個對象的明確特性為基礎(chǔ),單元測試的過程應(yīng)該限定在一個明確的線程范圍內(nèi)。根據(jù)上面所述,一個單元測試的測試過程非常類似于一個Use Case的定義,但是單元測試的粒度一般來說比Use Case的定義要小,這點是容易理解的,因為Use Case是以單獨的事務(wù)單元為基礎(chǔ)的,而單元測試是以一組聚合性很強的對象的特定特征為基礎(chǔ)的,一般而言一個事務(wù)中會利用許多的系統(tǒng)特征來完成具體的軟件需求。

從上面的分析我們可以得出,測試單元應(yīng)該以一個對象的內(nèi)部狀態(tài)的轉(zhuǎn)換為基本編寫單元。一個軟件系統(tǒng)和一輛設(shè)計好的汽車一樣,系統(tǒng)的狀態(tài)是由同一時刻時系統(tǒng)內(nèi)部的各個分立的部件的狀態(tài)決定的,因此為了確定一個系統(tǒng)終的行為符合我們起始的要求,我們首先需要保證系統(tǒng)內(nèi)的各個部分的狀態(tài)會符合我們的設(shè)計要求,所以我們的測試單元的重點應(yīng)該放在確定對象的狀態(tài)變換上。

然而需要注意的并不是所有的對象組特征都需要被編寫成獨立的測試單元,如何在對象組特征里篩選有價值的測試單元的原則在JUnitTest Infected: Programmers Love Writing Tests一文中得到了正確的描述,你應(yīng)該在有可能引入錯誤的地方引入測試單元,通常這些地方存在于有特定邊界條件、復(fù)雜算法以及需求變動比較頻繁的代碼邏輯中。除了這些特性需要被編寫成獨立的測試單元外,還有一些邊界條件比較復(fù)雜的對象方法也應(yīng)該被編寫成獨立的測試單元,這部分單元測試已經(jīng)在Junit文檔中被較好的描述和解釋過了。

在基本確定了需要編寫的單元測試,我們還應(yīng)該問自己:編寫好了這些測試,我們是否可以有把握地告訴自己,如果代碼通過了這些單元測試,我們能認定程序的運行是正確的,符合需求的。如果我們不能非常的確定,應(yīng)該看看是否還有遺漏的需要編寫的單元測試或者重新審視我們對軟件需求的理解。通常來說,在開始使用單元測試的時候,更多的單元測試總是沒有錯的。

一旦我們確定了需要被編寫的測試單元,接下來應(yīng)該

3. 如何編寫單元測試

在XP下強調(diào)單元測試必須由類包的編寫者負責(zé)編寫,這個限定對于我們設(shè)定的測試目標是必須的。因為只有這樣,測試才能保證對象的運行時態(tài)行為符合需求,而僅通過類接口的測試,我們只能確保對象符合靜態(tài)約束,因此這要求我們在測試的過程中,必須開放一定的內(nèi)部數(shù)據(jù)結(jié)構(gòu),或者針對特定的運行行為建立適當?shù)臄?shù)據(jù)記錄,并把這些數(shù)據(jù)暴露給特定的測試單元。這也是說我們在編寫單元測試時必須對相應(yīng)的類包進行修改,這樣的修改也發(fā)生在我們以前使用的測試方法中,因此以前的測試標記及其他一些測試技巧仍然可以在Junit測試中改進使用。

由于單元測試的總體目標是負責(zé)我們的軟件在運行過程中的正確無誤,因此在我們對一個對象編寫單元測試的時候,我們不但需要保證類的靜態(tài)約束符合我們的設(shè)計意圖,而且需要保證對象在特定的條件下的運行狀態(tài)符合我們的預(yù)先設(shè)定。還是拿數(shù)據(jù)庫緩沖池的例子說明,一個緩沖池暴露給其他對象的是一組使用接口,其中包括對池的參數(shù)設(shè)定、池的初始化、池的銷毀、從這個池里獲得一個數(shù)據(jù)連接以及釋放連接到池中,對其他對象而言隨著各種條件的觸發(fā)而引起池的內(nèi)部狀態(tài)的變化是不需要知道的,這一點也是符合封裝原理的。但是池對象的狀態(tài)變化,譬如:緩存的連接數(shù)在某些條件下會增長,一個連接在足夠長的運行后需要被徹底釋放從而使池的連接被更新等等,雖然外部對象不需要明確,但是卻是程序運行正確的保證,所以我們的單元測試必須保證這些內(nèi)部邏輯被正確的運行。

編譯語言的測試和調(diào)試是很難對運行的邏輯過程進行跟蹤的,但是我們知道,無論邏輯怎么運行,如果狀態(tài)的轉(zhuǎn)換符合我們的行為設(shè)定,那驗證結(jié)果顯然是正確的,因此在對一個對象進行單元測試的時候,我們需要對多數(shù)的狀態(tài)轉(zhuǎn)換進行分析和對照,從而驗證對象的行為。狀態(tài)是通過一系列的狀態(tài)數(shù)據(jù)來描述的,因此編寫單元測試首先分析出狀態(tài)的變化過程(狀態(tài)轉(zhuǎn)換圖對這個過程的描述非常清晰),然后根據(jù)狀態(tài)的定義確定分析的狀態(tài)數(shù)據(jù),后是提供這些內(nèi)部的狀態(tài)數(shù)據(jù)的訪問。在數(shù)據(jù)庫連接池的例子中,我們對池實現(xiàn)的對象DefaultConnectionProxy的狀態(tài)變換進行分析后,我們決定把表征狀態(tài)的OracleConnectionCacheImpl對象公開給測試類。參見示例一

示例一
/**
* 這個類簡單的包裝了oracle對數(shù)據(jù)連接緩沖池的實現(xiàn)。
*
*/
public class DefaultConnectionProxy extends ConnectionProxy {

private static final String name = "Default Connection Proxy";
private static final String description = "這個類簡單的包裝了oracle對數(shù)據(jù)連接緩沖池的實現(xiàn)。";
private static final String author = "Ion-Global.com";
private static final int major_version = 0;
private static final int minor_version = 9;
private static final boolean pooled = true;

private ConnectionBroker connectionBroker = null;
private Properties props;
private Properties propDescriptions;

private Object initLock = new Object();

// Test Code Begin...
/* 為了能夠了解對象的狀態(tài)變化,因此需要把表征對象內(nèi)部狀態(tài)變化的部分私有變量提供公共的訪問接口
  (或者提供讓同一個類包的訪問接口),以便使測試單元可以有效地判斷對象的狀態(tài)轉(zhuǎn)變,
   在本示例中對包裝的OracleConnectionCacheImpl對象提供訪問接口。
*/
OracleConnectionCacheImpl getConnectionCache() {
if (connectionBroker == null) {
throw new IllegalStateException("You need start the server first.");
}

return connectionBroker.getConnectionCache();
}


// Test Code End...

上一頁123下一頁
軟件測試工具 | 聯(lián)系我們 | 投訴建議 | 誠聘英才 | 申請使用列表 | 網(wǎng)站地圖
滬ICP備07036474 2003-2017 版權(quán)所有 上海澤眾軟件科技有限公司 Shanghai ZeZhong Software Co.,Ltd