EasyMock使用方法與原理剖析

EasyMock 是一套通過簡單的方法對於指定的接口或類生成 Mock 對象的類庫,它能利用對接口或類的模擬來輔助單元測試。本文將對 EasyMock 的功能和原理進行介紹,並通過示例來說明如何使用 EasyMock 進行單元測試

  Mock 方法是單元測試中常見的一種技術,它的主要作用是模擬一些在應用中不容易構造或者比較複雜的對象,從而把測試與測試邊界以外的對象隔離開。

  編寫自定義的 Mock 對象需要額外的編碼工作,同時也可能引入錯誤。EasyMock 提供了根據指定接口動態構建 Mock 對象的方法,避免了手工編寫 Mock 對象。本文將向您展示如何使用 EasyMock 進行單元測試,並對 EasyMock 的原理進行分析。

1.Mock 對象與 EasyMock 簡介

單元測試與 Mock 方法

  單元測試是對應用中的某一個模塊的功能進行驗證。在單元測試中,我們常遇到的問題是應用中其它的協同模塊尚未開發完成,或者被測試模塊需要和一些不容易構造、比較複雜的對象進行交互。另外,由於不能肯定其它模塊的正確性,我們也無法確定測試中發現的問題是由哪個模塊引起的。

  Mock 對象能夠模擬其它協同模塊的行爲,被測試模塊通過與 Mock 對象協作,可以獲得一個孤立的測試環境。此外,使用 Mock 對象還可以模擬在應用中不容易構造(如 HttpServletRequest 必須在 Servlet 容器中才能構造出來)和比較複雜的對象(如 JDBC 中的 ResultSet 對象),從而使測試順利進行。

EasyMock 簡介

  手動的構造 Mock 對象會給開發人員帶來額外的編碼量,而且這些爲創建 Mock 對象而編寫的代碼很有可能引入錯誤。目前,有許多開源項目對動態構建 Mock 對象提供了支持,這些項目能夠根據現有的接口或類動態生成,這樣不僅能避免額外的編碼工作,同時也降低了引入錯誤的可能。

  EasyMock 是一套用於通過簡單的方法對於給定的接口生成 Mock 對象的類庫。它提供對接口的模擬,能夠通過錄制、回放、檢查三步來完成大體的測試過程,可以驗證方法的調用種類、次數、順序,可以令 Mock 對象返回指定的值或拋出指定異常。通過 EasyMock,我們可以方便的構造 Mock 對象從而使單元測試順利進行。

安裝 EasyMock

  EasyMock 是採用 MIT license 的一個開源項目,您可以在 Sourceforge 上下載到相關的 zip 文件。目前您可以下載的 EasyMock 最新版本是2.3,它需要運行在 Java 5.0 平臺上。如果您的應用運行在 Java 1.3 或 1.4 平臺上,您可以選擇 EasyMock1.2。在解壓縮 zip 包後,您可以找到 easymock.jar 這個文件。如果您使用 Eclipse 作爲 IDE,把 easymock.jar 添加到項目的 Libraries 裏就可以使用了(如下圖所示)。此外,由於我們的測試用例運行在 JUnit 環境中,因此您還需要 JUnit.jar(版本3.8.1以上)。


圖1:Eclipse 項目中的 Libraries
Eclipse 項目中的 Libraries

2.使用 EasyMock 進行單元測試

[NextPage]

  通過 EasyMock,我們可以爲指定的接口動態的創建 Mock 對象,並利用 Mock 對象來模擬協同模塊或是領域對象,從而使單元測試順利進行。這個過程大致可以劃分爲以下幾個步驟:

  • 使用 EasyMock 生成 Mock 對象;
  • 設定 Mock 對象的預期行爲和輸出;
  • 將 Mock 對象切換到 Replay 狀態;
  • 調用 Mock 對象方法進行單元測試;
  • 對 Mock 對象的行爲進行驗證。

  接下來,我們將對以上的幾個步驟逐一進行說明。除了以上的基本步驟外,EasyMock 還對特殊的 Mock 對象類型、特定的參數匹配方式等功能提供了支持,我們將在之後的章節中進行說明。

使用 EasyMock 生成 Mock 對象

  根據指定的接口或類,EasyMock 能夠動態的創建 Mock 對象(EasyMock 默認只支持爲接口生成 Mock 對象,如果需要爲類生成 Mock 對象,在 EasyMock 的主頁上有擴展包可以實現此功能),我們以 ResultSet 接口爲例說明EasyMock的功能。java.sql.ResultSet 是每一個 Java 開發人員都非常熟悉的接口:


清單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;
......
}

  通常,構建一個真實的 RecordSet 對象需要經過一個複雜的過程:在開發過程中,開發人員通常會編寫一個 DBUtility 類來獲取數據庫連接 Connection,並利用 Connection 創建一個 Statement。執行一個 Statement 可以獲取到一個或多個 ResultSet 對象。這樣的構造過程複雜並且依賴於數據庫的正確運行。數據庫或是數據庫交互模塊出現問題,都會影響單元測試的結果。

  我們可以使用 EasyMock 動態構建 ResultSet 接口的 Mock 對象來解決這個問題。一些簡單的測試用例只需要一個 Mock 對象,這時,我們可以用以下的方法來創建 Mock 對象:

ResultSet mockResultSet = createMock(ResultSet.class);

 

  其中 createMock 是 org.easymock.EasyMock 類所提供的靜態方法,你可以通過 static import 將其引入(注:static import 是 java 5.0 所提供的新特性)。

  如果需要在相對複雜的測試用例中使用多個 Mock 對象,EasyMock 提供了另外一種生成和管理 Mock 對象的機制:

IMocksControl control = EasyMock.createControl();
java.sql.Connection mockConnection = control.createMock(Connection.class);
java.sql.Statement mockStatement = control.createMock(Statement.class);
java.sql.ResultSet mockResultSet = control.createMock(ResultSet.class);

 

  EasyMock 類的 createControl 方法能創建一個接口 IMocksControl 的對象,該對象能創建並管理多個 Mock 對象。如果需要在測試中使用多個 Mock 對象,我們推薦您使用這一機制,因爲它在多個 Mock 對象的管理上提供了相對便捷的方法。

  如果您要模擬的是一個具體類而非接口,那麼您需要下載擴展包 EasyMock Class Extension 2.2.2。在對具體類進行模擬時,您只要用 org.easymock.classextension.EasyMock 類中的靜態方法代替 org.easymock.EasyMock 類中的靜態方法即可。

設定 Mock 對象的預期行爲和輸出

  在一個完整的測試過程中,一個 Mock 對象將會經歷兩個狀態:Record 狀態和 Replay 狀態。Mock 對象一經創建,它的狀態就被置爲 Record。在 Record 狀態,用戶可以設定 Mock 對象的預期行爲和輸出,這些對象行爲被錄製下來,保存在 Mock 對象中。

  添加 Mock 對象行爲的過程通常可以分爲以下3步:

  • 對 Mock 對象的特定方法作出調用;
  • 通過 org.easymock.EasyMock 提供的靜態方法 expectLastCall 獲取上一次方法調用所對應的 IExpectationSetters 實例;
  • 通過 IExpectationSetters 實例設定 Mock 對象的預期輸出。

     

    設定預期返回值

  Mock 對象的行爲可以簡單的理解爲 Mock 對象方法的調用和方法調用所產生的輸出。在 EasyMock 2.3 中,對 Mock 對象行爲的添加和設置是通過接口 IExpectationSetters 來實現的。Mock 對象方法的調用可能產生兩種類型的輸出:(1)產生返回值;(2)拋出異常。接口 IExpectationSetters 提供了多種設定預期輸出的方法,其中和設定返回值相對應的是 andReturn 方法:

IExpectationSetters<T> andReturn(T value);

 

  我們仍然用 ResultSet 接口的 Mock 對象爲例,如果希望方法 mockResult.getString(1) 的返回值爲 "My return value",那麼你可以使用以下的語句:

mockResultSet.getString(1);
expectLastCall().andReturn("My return value");

 

  以上的語句表示 mockResultSet 的 getString 方法被調用一次,這次調用的返回值是 "My return value"。有時,我們希望某個方法的調用總是返回一個相同的值,爲了避免每次調用都爲 Mock 對象的行爲進行一次設定,我們可以用設置默認返回值的方法:

void andStubReturn(Object value);

 

  假設我們創建了 Statement 和 ResultSet 接口的 Mock 對象 mockStatement 和 mockResultSet,在測試過程中,我們希望 mockStatement 對象的 executeQuery 方法總是返回 mockResultSet,我們可以使用如下的語句

mockStatement.executeQuery("SELECT * FROM sales_order_table");
expectLastCall().andStubReturn(mockResultSet);

 

  EasyMock 在對參數值進行匹配時,默認採用 Object.equals() 方法。因此,如果我們以 "select * from sales_order_table" 作爲參數,預期方法將不會被調用。如果您希望上例中的 SQL 語句能不區分大小寫,可以用特殊的參數匹配器來解決這個問題,我們將在 "在 EasyMock 中使用參數匹配器" 一章對此進行說明。

設定預期異常拋出

  對象行爲的預期輸出除了可能是返回值外,還有可能是拋出異常。IExpectationSetters 提供了設定預期拋出異常的方法:

IExpectationSetters<T> andThrow(Throwable throwable);
[NextPage]

 

  和設定默認返回值類似,IExpectationSetters 接口也提供了設定拋出默認異常的函數:

void andStubThrow(Throwable throwable);

 

設定預期方法調用次數

  通過以上的函數,您可以對 Mock 對象特定行爲的預期輸出進行設定。除了對預期輸出進行設定,IExpectationSetters 接口還允許用戶對方法的調用次數作出限制。在 IExpectationSetters 所提供的這一類方法中,常用的一種是 times 方法:

IExpectationSetters<T>times(int count);

 

  該方法可以 Mock 對象方法的調用次數進行確切的設定。假設我們希望 mockResultSet 的 getString 方法在測試過程中被調用3次,期間的返回值都是 "My return value",我們可以用如下語句:

mockResultSet.getString(1);
expectLastCall().andReturn("My return value").times(3);


  注意到 andReturn 和 andThrow 方法的返回值依然是一個 IExpectationSetters 實例,因此我們可以在此基礎上繼續調用 times 方法。

 

  除了設定確定的調用次數,IExpectationSetters 還提供了另外幾種設定非準確調用次數的方法:
times(int minTimes, int maxTimes):該方法最少被調用 minTimes 次,最多被調用 maxTimes 次。
atLeastOnce():該方法至少被調用一次。
anyTimes():該方法可以被調用任意次。

  某些方法的返回值類型是 void,對於這一類方法,我們無需設定返回值,只要設置調用次數就可以了。以 ResultSet 接口的 close 方法爲例,假設在測試過程中,該方法被調用3至5次:

mockResultSet.close();
expectLastCall().times(3, 5);

 

  爲了簡化書寫,EasyMock 還提供了另一種設定 Mock 對象行爲的語句模式。對於上例,您還可以將它寫成:

  這個語句和上例中的語句功能是完全相同的。

將 Mock 對象切換到 Replay 狀態

  在生成 Mock 對象和設定 Mock 對象行爲兩個階段,Mock 對象的狀態都是 Record 。在這個階段,Mock 對象會記錄用戶對預期行爲和輸出的設定。

  在使用 Mock 對象進行實際的測試前,我們需要將 Mock 對象的狀態切換爲 Replay。在 Replay 狀態,Mock 對象能夠根據設定對特定的方法調用作出預期的響應。將 Mock 對象切換成 Replay 狀態有兩種方式,您需要根據 Mock 對象的生成方式進行選擇。如果 Mock 對象是通過 org.easymock.EasyMock 類提供的靜態方法 createMock 生成的(第1節中介紹的第一種 Mock 對象生成方法),那麼 EasyMock 類提供了相應的 replay 方法用於將 Mock 對象切換爲 Replay 狀態:

replay(mockResultSet);
[NextPage]
  如果 Mock 對象是通過 IMocksControl 接口提供的 createMock 方法生成的(第1節中介紹的第二種Mock對象生成方法),那麼您依舊可以通過 IMocksControl 接口對它所創建的所有 Mock 對象進行切換:

 

  以上的語句能將在第1節中生成的 mockConnection、mockStatement 和 mockResultSet 等3個 Mock 對象都切換成 Replay 狀態。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章