您的位置:軟件測試 > 開源軟件測試 > 開源單元測試工具 > junit
怎樣使用Junit Framework進行單元測試的編寫
作者:網絡轉載 發(fā)布時間:[ 2013/4/3 14:14:29 ] 推薦標簽:

1. 單元測試的編寫原則

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

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

2. 如何確定單元測試

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

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

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

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

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

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

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

3. 如何編寫單元測試

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

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

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

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


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

對數據庫連接池的例子,我們可以建立測試類 DefaultConnectionProxyTest,同時建立數個 test case,如下:

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

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