Mock 方法是單元測(cè)試中常見(jiàn)的一種技術(shù),它的主要作用是模擬一些在應(yīng)用中不容易構(gòu)造或者比較復(fù)雜的對(duì)象,從而把測(cè)試與測(cè)試邊界以外的對(duì)象隔離開(kāi)。
編寫(xiě)自定義的 Mock 對(duì)象需要額外的編碼工作,同時(shí)也可能引入錯(cuò)誤。EasyMock 提供了根據(jù)指定接口動(dòng)態(tài)構(gòu)建 Mock 對(duì)象的方法,避免了手工編寫(xiě) Mock 對(duì)象。本文將向您展示如何使用 EasyMock 進(jìn)行單元測(cè)試,并對(duì) EasyMock 的原理進(jìn)行分析。
1.Mock 對(duì)象與 EasyMock 簡(jiǎn)介
單元測(cè)試與 Mock 方法
單元測(cè)試是對(duì)應(yīng)用中的某一個(gè)模塊的功能進(jìn)行驗(yàn)證。在單元測(cè)試中,我們常遇到的問(wèn)題是應(yīng)用中其它的協(xié)同模塊尚未開(kāi)發(fā)完成,或者被測(cè)試模塊需要和一些不容易構(gòu)造、比較復(fù)雜的對(duì)象進(jìn)行交互。另外,由于不能肯定其它模塊的正確性,我們也無(wú)法確定測(cè)試中發(fā)現(xiàn)的問(wèn)題是由哪個(gè)模塊引起的。
Mock 對(duì)象能夠模擬其它協(xié)同模塊的行為,被測(cè)試模塊通過(guò)與 Mock 對(duì)象協(xié)作,可以獲得一個(gè)孤立的測(cè)試環(huán)境。此外,使用 Mock 對(duì)象還可以模擬在應(yīng)用中不容易構(gòu)造(如 HttpServletRequest 必須在 Servlet 容器中才能構(gòu)造出來(lái))和比較復(fù)雜的對(duì)象(如 JDBC 中的 ResultSet 對(duì)象),從而使測(cè)試順利進(jìn)行。
EasyMock 簡(jiǎn)介
手動(dòng)的構(gòu)造 Mock 對(duì)象會(huì)給開(kāi)發(fā)人員帶來(lái)額外的編碼量,而且這些為創(chuàng)建 Mock 對(duì)象而編寫(xiě)的代碼很有可能引入錯(cuò)誤。目前,有許多開(kāi)源項(xiàng)目對(duì)動(dòng)態(tài)構(gòu)建 Mock 對(duì)象提供了支持,這些項(xiàng)目能夠根據(jù)現(xiàn)有的接口或類動(dòng)態(tài)生成,這樣不僅能避免額外的編碼工作,同時(shí)也降低了引入錯(cuò)誤的可能。
EasyMock 是一套用于通過(guò)簡(jiǎn)單的方法對(duì)于給定的接口生成 Mock 對(duì)象的類庫(kù)。它提供對(duì)接口的模擬,能夠通過(guò)錄制、回放、檢查三步來(lái)完成大體的測(cè)試過(guò)程,可以驗(yàn)證方法的調(diào)用種類、次數(shù)、順序,可以令 Mock 對(duì)象返回指定的值或拋出指定異常。通過(guò) EasyMock,我們可以方便的構(gòu)造 Mock 對(duì)象從而使單元測(cè)試順利進(jìn)行。
安裝 EasyMock
EasyMock 是采用 MIT license 的一個(gè)開(kāi)源項(xiàng)目,您可以在 Sourceforge 上下載到相關(guān)的 zip 文件。目前您可以下載的 EasyMock 新版本是2.3,它需要運(yùn)行在 Java 5.0 平臺(tái)上。如果您的應(yīng)用運(yùn)行在 Java 1.3 或 1.4 平臺(tái)上,您可以選擇 EasyMock1.2。在解壓縮 zip 包后,您可以找到 easymock.jar 這個(gè)文件。如果您使用 Eclipse 作為 IDE,把 easymock.jar 添加到項(xiàng)目的 Libraries 里可以使用了(如下圖所示)。此外,由于我們的測(cè)試用例運(yùn)行在 JUnit 環(huán)境中,因此您還需要 JUnit.jar(版本3.8.1以上)。
圖1:Eclipse 項(xiàng)目中的 Libraries
2.使用 EasyMock 進(jìn)行單元測(cè)試
通過(guò) EasyMock,我們可以為指定的接口動(dòng)態(tài)的創(chuàng)建 Mock 對(duì)象,并利用 Mock 對(duì)象來(lái)模擬協(xié)同模塊或是領(lǐng)域?qū)ο,從而使單元測(cè)試順利進(jìn)行。這個(gè)過(guò)程大致可以劃分為以下幾個(gè)步驟:
使用 EasyMock 生成 Mock 對(duì)象;
設(shè)定 Mock 對(duì)象的預(yù)期行為和輸出;
將 Mock 對(duì)象切換到 Replay 狀態(tài);
調(diào)用 Mock 對(duì)象方法進(jìn)行單元測(cè)試;
對(duì) Mock 對(duì)象的行為進(jìn)行驗(yàn)證。
接下來(lái),我們將對(duì)以上的幾個(gè)步驟逐一進(jìn)行說(shuō)明。除了以上的基本步驟外,EasyMock 還對(duì)特殊的 Mock 對(duì)象類型、特定的參數(shù)匹配方式等功能提供了支持,我們將在之后的章節(jié)中進(jìn)行說(shuō)明。
使用 EasyMock 生成 Mock 對(duì)象
根據(jù)指定的接口或類,EasyMock 能夠動(dòng)態(tài)的創(chuàng)建 Mock 對(duì)象(EasyMock 默認(rèn)只支持為接口生成 Mock 對(duì)象,如果需要為類生成 Mock 對(duì)象,在 EasyMock 的主頁(yè)上有擴(kuò)展包可以實(shí)現(xiàn)此功能),我們以 ResultSet 接口為例說(shuō)明EasyMock的功能。java.sql.ResultSet 是每一個(gè) Java 開(kāi)發(fā)人員都非常熟悉的接口:
清單1:ResultSet 接口
public interface java.sql.ResultSet {
......
public abstract java.lang.String getString(int arg0) throws java.sql.SQLException;
public abstract double getDouble(int arg0) throws java.sql.SQLException;
......
}
通常,構(gòu)建一個(gè)真實(shí)的 RecordSet 對(duì)象需要經(jīng)過(guò)一個(gè)復(fù)雜的過(guò)程:在開(kāi)發(fā)過(guò)程中,開(kāi)發(fā)人員通常會(huì)編寫(xiě)一個(gè) DBUtility 類來(lái)獲取數(shù)據(jù)庫(kù)連接 Connection,并利用 Connection 創(chuàng)建一個(gè) Statement。執(zhí)行一個(gè) Statement 可以獲取到一個(gè)或多個(gè) ResultSet 對(duì)象。這樣的構(gòu)造過(guò)程復(fù)雜并且依賴于數(shù)據(jù)庫(kù)的正確運(yùn)行。數(shù)據(jù)庫(kù)或是數(shù)據(jù)庫(kù)交互模塊出現(xiàn)問(wèn)題,都會(huì)影響單元測(cè)試的結(jié)果。
我們可以使用 EasyMock 動(dòng)態(tài)構(gòu)建 ResultSet 接口的 Mock 對(duì)象來(lái)解決這個(gè)問(wèn)題。一些簡(jiǎn)單的測(cè)試用例只需要一個(gè) Mock 對(duì)象,這時(shí),我們可以用以下的方法來(lái)創(chuàng)建 Mock 對(duì)象:
ResultSet mockResultSet = createMock(ResultSet.class);
其中 createMock 是 org.easymock.EasyMock 類所提供的靜態(tài)方法,你可以通過(guò) static import 將其引入(注:static import 是 java 5.0 所提供的新特性)。