EasyMock入門使用


EasyMock入門使用

一、EasyMock安裝

  1. EasyMock是一套通過簡單的方法對於指定的接口或類生成Mock對象的類庫,它能利用對接口或類的模擬來輔助單元測試。

  2. 要使用EasyMock輔助單元測試,添加easymock的jar包即可。

    image-20200402190315174

二、EasyMock使用

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

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

2.1 使用EasyMock生成Mock對象

根據指定的接口或類,EasyMock能動態地創建Mock對象。

2.1.1 為接口生成Mock對象

Eg:以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 對象來解決這個問題。

static import org.easymock.Easymock; //靜態方法引入
ResultSet mockResultSet = createMock(ResultSet.class);

import org.easymock.Easymock; 
ResultSet mockResultSet = Easymock.createMock(ResultSet.class);

2.1.2 為類生成Mock對象

EasyMock默認只支持為接口生成Mock對象,若要為類生成Mock對象,需下載擴展包 EasyMock Class Extension,在對具體類進行模擬時,只需把 org.easymock.EasyMock 替換為 org.easymock.classextension.Easymock

2.1.3 使用IMocksControl對象管理Mock對象

若在相對復雜的測試用例中使用多個Mock對象,可使用EasyMock提供的生成和管理Mock對象的機制。

EasyMock類的createControl方法能創建一個接口IMocksControl的對象,此對象能創建並管理多個Mock對象。

Eg:

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);

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

  1. 在一個完整測試中,一個Mock對象會經歷兩個狀態:Record狀態和Replay狀態。

    Mock對象一經創建,狀態為Record,Record狀態下用戶可以設定Mock對象的預期行為和輸出,這些對象行為會被錄制下來,保存在Mock對象中。

  2. 添加Mock對象行為的過程分為三步:

    • 對Mock對象的特定方法作出調用
    • 通過org.easymock.EasyMock提供的靜態方法expectLastCall獲取上一次方法調用所對應的IExpectionSetters實例
    • 用過IExpectionSetters實例設定Mock對象的預期輸出(有兩種類型)
      • 產生返回值
      • 拋出異常

    (Mock對象的行為可簡單理解為Mock對象的調用和方法調用所產生的輸出。)

2.2.1 設定預期返回值

設定返回值對應接口IExpectionSettersandReturn方法。

IExpectationSetters<T> andReturn(T value);
2.2.1.1 返回值不是void
  1. Eg:仍用 ResultSet 接口的 Mock 對象為例,若希望方法 mockResultSet.getString(1) 的返回值為 "My return value",則:

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

    或:

    EasyMock.expect(mockResultSet.getString(1)).andReturn("My return value");
    
  2. 若希望 某方法的調用總是返回一個相同的值 ,為避免每次調用都為Mock對象的行為進行一次設定,可用默認返回值的方法——andSubReturn方法。

    	void andStubReturn(Object value);
    

    Eg:假設我們創建了 StatementResultSet 接口的 Mock 對象 mockStatement 和 mockResultSet,在測試過程中,我們希望 mockStatement 對象的 executeQuery 方法總是返回 mockResultSet,則:

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

    或:

    EasyMock.expect(mockStatement.executeQuery("SELECT * FROM sales_order_table")).andStubReturn(mockResultSet);
    
2.2.1.2 返回值為void

若方法的返回值類型為void,則對於此類方法,我們無需設定返回值,只需設置調用次數就可以。(也可以不設置)

How設定調用次數,詳看2.2.3。

Eg:以 ResultSet 接口的 close 方法為例,假設在測試過程中,該方法被調用3至5次,則:

mockResultSet.close();
EasyMock.expectLastCall().times(3,5);// 最新版本的EasyMock可以忽略此句

或:

EasyMock.expect(mockResult.close()).times(3, 5);

2.2.2 設定預期異常拋出

設定預期拋出異常對應接口IExpectionSettersandThrow方法。

IExpectationSetters<T> andThrow(Throwable throwable);

類似的,設定拋出默認異常對應接口IExpectionSettersandStubThrow方法。

void andStubThrow(Throwable throwable);

Eg:

EasyMock.expectLastCall().andThrow(
                new MyException(new RuntimeException())).anyTimes();

2.2.3 設定預期方法調用次數

  1. 設定確定的調用次數:通過接口IExpectionSetterstimes方法。
IExpectationSetters<T>times(int count);

Eg:我們希望 mockResultSet 的 getString 方法在測試過程中被調用3次,期間的返回值都是 "My return value",則:

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

或:

EasyMock.expect(mockResultSet.getString(1)).andReturn("My return value").times(3);
  1. 設定非准確調用次數:
    • times(int minTimes, int maxTimes):該方法最少被調用 minTimes 次,最多被調用 maxTimes 次。
    • atLeastOnce():該方法至少被調用一次。
    • anyTimes():該方法可以被調用任意次。

2.2.4 若返回結果在運行時才能確定

很可能某個方法期望的返回結果不是固定的,例如根據傳入參數不同而不同;這時需要使用andAnswer()方法:

EasyMock.expect(mockService.execute(EasyMock.anyInt())).andAnswer(new IAnswer<Integer>() {
    public Integer answer() throws Throwable {
        Integer count = (Integer) EasyMock.getCurrentArguments()[0];
        return count * 2;
    }
});

{% note warning %}

​ 注意:通過EasyMock.getCurrentArguments()可以獲取傳入參數!

{% endnote %}

2.3 將Mock對象切換到Replay狀態——replay

  1. 在生成Mock對象和設定Mock對象行為的兩個階段,Mock對象的狀態均為Record,此階段Mock對象會記錄用戶對預期行為和輸出的設定。

  2. 在使用Mock對象隱形實際的測試前,需將Mock對象的狀態切換為Replay,此階段Mock對象能根據設定對特定的方法調用作出預期的響應。

  3. 將對象切換到Replay狀態有兩種方法:

    • 若Mock對象是通過createMock方法生成

      EasyMock.replay(mockResultSet);
      
    • 若Mock對象通過createControl方法生成的接口對象control的createMock方法生成

      control.replay();
      

      此語句可將通過coltrol的createMock方法生成的所有Mock對象均切換為Replay狀態。

2.4 調用Mock對象方法進行單元測試

此部分放到JUnit+EasyMock實例中。

2.5 對Mock對象的行為進行驗證——verify

在利用Mock對象進行實際的測試過程后,還需對Mock對象的方法調用的次數進行驗證。

  • 若Mock對象是通過createMock方法生成

    EasyMock.verify(mockResultSet);
    
  • 若Mock對象通過createControl方法生成的接口對象control的createMock方法生成

    control.verify();
    

    同理,此語句可驗證通過coltrol的createMock方法生成的所有Mock對象方法的調用次數。

2.6 Mock對象的重用——reset

為避免生成過多的Mock對象,EasyMock允許對原有的Mock對象進行重用。

可使用reset方法對Mock對象重新初始化。重新初始化后,Mock對象被置為Record狀態。

  • 若Mock對象是通過createMock方法生成

    EasyMock.reset(mockResultSet);
    
  • 若Mock對象通過createControl方法生成的接口對象control的createMock方法生成

    control.reset();
    

    同理,此語句可驗證通過coltrol的createMock方法生成的所有Mock對象重新初始化。


三、在EasyMock中使用參數匹配器

使用Mock對象進行實際的測試過程中,EasyMock會根據方法名和參數來匹配一個預期方法的調用。

此時,EasyMock對參數的匹配默認使用equals()方法進行比較,這可能會引起一些問題,因此EasyMock提供了一些參數匹配方式。

{% note info %}

如2.2.1.1中創建的mockStatement對象:

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

實際調用中,可能會遇到SQL語句中某些關鍵詞大小寫問題(因為SQL語句不區分大小寫)如將SELECT寫成select,此時EasyMock采用的默認匹配器equals方法將認為參數不匹配,則Mock對象的預期方法不會被調用。

{% endnote %}

3.1 EasyMock預定義的參數匹配器

  1. anyObject():任意輸入值都與預期值匹配;
  2. aryEq(X value):通過Arrays.equals()進行匹配,適用於數組對象;
  3. isNull():當輸入值為Null時匹配;
  4. notNull():當輸入值不為Null時匹配;
  5. same(X value):當輸入值和預期值是同一個對象時匹配;
  6. lt(X value), leq(X value), geq(X value), gt(X value):當輸入值小於、小等於、大等於、大於預期值時匹配,適用於數值類型;
  7. startsWith(String prefix), contains(String substring), endsWith(String suffix):當輸入值以預期值開頭、包含預期值、以預期值結尾時匹配,適用於String類型;
  8. matches(String regex):當輸入值與正則表達式匹配時匹配,適用於String類型。

例如:若我對mockStatement具體執行的語句並不關注,希望所有輸入的字符串都能夠匹配這一方法的調用

EasyMock.expect(mockStatement.executeQuery(anyObject())).andStubReturn(mockResultSet);

詳細參閱 easymock教程-參數匹配

3.2 EasyMock自定義參數匹配器

  1. 預定義的參數匹配器無法滿足一些復雜情況,此時需自己定義參數匹配器。

    如:在3.1中我們希望有一個匹配器對SQL中關鍵詞的大小寫不敏感,使用anyObject其實並不好,此時我們可以自定義參數匹配器SQLEquals。

  2. How自定義參數匹配器:

    • 實現 org.easymock.IArgumentMatcher 接口

      • matches(Object actual) 方法應當實現輸入值和預期值的匹配邏輯

      • appendTo(StringBuffer buffer) 方法可以添加當匹配失敗時需要顯示的信息

    • 使用靜態方法包裝實現接口的類

     package org.easymock.demo.matcher;
     import static org.easymock.EasyMock.reportMatcher;
     import org.easymock.IArgumentMatcher;
     //實現IArgumentMatcher接口
     public class SQLEquals implements IArgumentMatcher { 
        private String expectedSQL = null;
        public SQLEquals(String expectedSQL) {
            this.expectedSQL = expectedSQL;
        }
        //當匹配失敗時需要顯示的信息
        public void appendTo(StringBuffer buffer) {
            buffer.append("SQLEquals(\"" + expectedSQL + "\")");
        }
        //輸入值和預期值的匹配邏輯
        public boolean matches(Object actualSQL) {
            if (actualSQL == null && expectedSQL == null) return true;
            else if (actualSQL instanceof String) return expectedSQL.equalsIgnoreCase((String) actualSQL);
            else return false;
        }
        //自定義參數匹配器SQLEquals靜態方法	
        public static String sqlEquals(String in) { 
            reportMatcher(new SQLEquals(in));
            return in;
        }
     }
    

    使用自定義的sqlEquals匹配器:

    EasyMock.expect(mockStatement.executeQuery(sqlEquals("SELECT * FROM sales_order_table"))).andStubReturn(mockResultSet);
    

四、特殊的Mock對象類型

上述創建的Mock對象都屬於EasyMock默認的Mock對象類型,它對預期方法的調用順序不敏感,對非預期的方法調用拋出AssertionError。

除此默認類型,EasyMock還提供一些特殊的Mock類型用於支持不同的需求。

4.1 Strick Mock對象

Stick Mock對象——對方法調用的先后順序敏感,創建方法如下:

  1. 使用 EasyMock.createStrickMock() 來創建:

    ResultSet strickMockResultSet = EasyMock.createStrickMock(ResultSet.class);
    
  2. 類似於createMock,同樣可用IMocksControl實例來創建一個Stick Mock對象:

    IMocksControl control = EasyMock.createStrictControl();
    ResultSet strickMockResultSet = control.createMock(ResultSet.class);
    

4.2 Nice Mock對象

Nice Mock對象——默認返回0,null或false等“無效值”,創建方法如下:

  1. 使用 EasyMock.createNiceMock() 來創建:

    ResultSet strickMockResultSet = EasyMock.createNiceMock(ResultSet.class);
    
  2. 類似於createMock,同樣可用IMocksControl實例來創建一個Nice Mock對象:

    IMocksControl control = EasyMock.createNiceControl();
    ResultSet strickMockResultSet = control.createMock(ResultSet.class);
    

五、EasyMock 的工作原理

參閱 EasyMock使用方法與原理剖析


六、參考文獻

EasyMock使用方法與原理剖析

easymock教程-參數匹配

【JUnit】EasyMock用法總結


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM