您的位置:軟件測(cè)試 > 開源軟件測(cè)試 > 開源單元測(cè)試工具 > junit
怎樣使用Junit Framework進(jìn)行單元測(cè)試的編寫
作者:網(wǎng)絡(luò)轉(zhuǎn)載 發(fā)布時(shí)間:[ 2013/4/3 14:14:29 ] 推薦標(biāo)簽:

1. 單元測(cè)試的編寫原則

Junit 附帶文檔所列舉的單元測(cè)試帶有一定的迷惑性,因?yàn)閹缀跛械氖纠龁卧际轻槍?duì)某個(gè)對(duì)象的某個(gè)方法,似乎 Junit 的單元測(cè)試僅適用于類組織結(jié)構(gòu)的靜態(tài)約束,從而使初學(xué)者懷疑 Junit 下的單元測(cè)試所能帶來的效果。因此我們需要重新定義如何確定有價(jià)值的單元測(cè)試以及如何編寫這些單元測(cè)試、維護(hù)這些單元測(cè)試,從而讓更多的程序員接受和熟悉 Junit 下的單元測(cè)試的編寫。

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

2. 如何確定單元測(cè)試

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

    在連接池啟動(dòng)后,是否根據(jù)定義的規(guī)則在池中建立了相應(yīng)數(shù)量的數(shù)據(jù)庫連接
    申請(qǐng)一個(gè)數(shù)據(jù)庫連接,是否根據(jù)定義的規(guī)則從池中直接獲得緩存連接的引用,還是建立新的連接
    釋放一個(gè)數(shù)據(jù)庫連接后,連接是否根據(jù)定義的規(guī)則被池釋放或者緩存以便以后使用
    后臺(tái) Housekeeping 線程是否按照定義的規(guī)則釋放已經(jīng)過期的連接申請(qǐng)
    如果連接有時(shí)間期限,后臺(tái) Housekeeping 線程是否定期釋放已經(jīng)過期的緩存連接

這兒只列出了部分的可能測(cè)試,但是從這個(gè)列表我們可以看出單元測(cè)試的粒度。一個(gè)單元測(cè)試基本是以一個(gè)對(duì)象的明確特性為基礎(chǔ),單元測(cè)試的過程應(yīng)該限定在一個(gè)明確的線程范圍內(nèi)。根據(jù)上面所述,一個(gè)單元測(cè)試的測(cè)試過程非常類似于一個(gè) Use Case 的定義,但是單元測(cè)試的粒度一般來說比 Use Case 的定義要小,這點(diǎn)是容易理解的,因?yàn)?Use Case 是以單獨(dú)的事務(wù)單元為基礎(chǔ)的,而單元測(cè)試是以一組聚合性很強(qiáng)的對(duì)象的特定特征為基礎(chǔ)的,一般而言一個(gè)事務(wù)中會(huì)利用許多的系統(tǒng)特征來完成具體的軟件需求。

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

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

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

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

3. 如何編寫單元測(cè)試

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

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

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

示例一
 /**
 * 這個(gè)類簡(jiǎn)單的包裝了 oracle 對(duì)數(shù)據(jù)連接緩沖池的實(shí)現(xiàn)。
 *
 */
 public class DefaultConnectionProxy extends ConnectionProxy {
     private static final String name = "Default Connection Proxy";
     private static final String description =
   "這個(gè)類簡(jiǎn)單的包裝了 oracle 對(duì)數(shù)據(jù)連接緩沖池的實(shí)現(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...
     /* 為了能夠了解對(duì)象的狀態(tài)變化,因此需要把表征對(duì)象內(nèi)部狀態(tài)變化的部分私有變量提
  供公共的訪問接口(或者提供讓同一個(gè)類包的訪問接口),以便使測(cè)試單元可以有效地
  判斷對(duì)象的狀態(tài)轉(zhuǎn)變,在本示例中對(duì)包裝的 OracleConnectionCacheImpl 對(duì)象提供訪問
  接口。
     */
     OracleConnectionCacheImpl getConnectionCache() {
         if (connectionBroker == null) {
             throw new IllegalStateException("You need start the server first.");
         }
       
         return connectionBroker.getConnectionCache();
     }
     // Test Code End...


在公開內(nèi)部狀態(tài)數(shù)據(jù)後,我們可以編寫我們的測(cè)試單元了,單元測(cè)試的選擇方法和選擇尺度已經(jīng)在本文前面章節(jié)進(jìn)行了說明,但是仍然需要注意的是,由于 assert 方法會(huì)拋出一個(gè) error,你應(yīng)該在測(cè)試方法的后集中用 assert 相關(guān)方法進(jìn)行判斷,這樣可以確保資源得到釋放。

對(duì)數(shù)據(jù)庫連接池的例子,我們可以建立測(cè)試類 DefaultConnectionProxyTest,同時(shí)建立數(shù)個(gè) test case,如下:

示例二
 /**
 * 這個(gè)類對(duì)示例一中的類進(jìn)行簡(jiǎn)單的測(cè)試。
 *
 */
 public class DefaultConnectionProxyTest extends TestCase {
     private DefaultConnectionProxy conProxy = null;
     private OracleConnectionCacheImpl cacheImpl = null;
     private Connection con = null;
     /** 設(shè)置測(cè)試的 fixture,建立必要的測(cè)試起始環(huán)境。
     */
     protected void setUp() {
         conProxy = new DefaultConnectionProxy();
         conProxy.start();
         cacheImpl = conProxy.getConnectionCache();
     }
     /** 對(duì)示例一中的對(duì)象進(jìn)行服務(wù)啟動(dòng)后的狀態(tài)測(cè)試,檢查是否在服務(wù)啟動(dòng)后,
連接池的參數(shù)設(shè)置是否正確。

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