在重新初始化之后,Mock 對象的狀態(tài)將被置為 Record 狀態(tài)。
3.在 EasyMock 中使用參數匹配器
EasyMock 預定義的參數匹配器
在使用 Mock 對象進行實際的測試過程中,EasyMock 會根據方法名和參數來匹配一個預期方法的調用。EasyMock 對參數的匹配默認使用 equals() 方法進行比較。這可能會引起一些問題。例如在上一章節(jié)中創(chuàng)建的mockStatement對象:
mockStatement.executeQuery("SELECT * FROM sales_order_table");
expectLastCall().andStubReturn(mockResultSet);
在實際的調用中,我們可能會遇到 SQL 語句中某些關鍵字大小寫的問題,例如將 SELECT 寫成 Select,這時在實際的測試中,EasyMock 所采用的默認匹配器將認為這兩個參數不匹配,從而造成 Mock 對象的預期方法不被調用。EasyMock 提供了靈活的參數匹配方式來解決這個問題。如果您對 mockStatement 具體執(zhí)行的語句并不關注,并希望所有輸入的字符串都能匹配這一方法調用,您可以用 org.easymock.EasyMock 類所提供的 anyObject 方法來代替參數中的 SQL 語句:
mockStatement.executeQuery( anyObject() );
expectLastCall().andStubReturn(mockResultSet);
anyObject 方法表示任意輸入值都與預期值相匹配。除了 anyObject 以外,EasyMock還提供了多個預先定義的參數匹配器,其中比較常用的一些有:
aryEq(X value):通過Arrays.equals()進行匹配,適用于數組對象;
isNull():當輸入值為Null時匹配;
notNull():當輸入值不為Null時匹配;
same(X value):當輸入值和預期值是同一個對象時匹配;
lt(X value), leq(X value), geq(X value), gt(X value):當輸入值小于、小等于、大等于、大于預期值時匹配,適用于數值類型;
startsWith(String prefix), contains(String substring), endsWith(String suffix):當輸入值以預期值開頭、包含預期值、以預期值結尾時匹配,適用于String類型;
matches(String regex):當輸入值與正則表達式匹配時匹配,適用于String類型。
自定義參數匹配器
預定義的參數匹配器可能無法滿足一些復雜的情況,這時你需要定義自己的參數匹配器。在上一節(jié)中,我們希望能有一個匹配器對 SQL 中關鍵字的大小寫不敏感,使用 anyObject 其實并不是一個好的選擇。對此,我們可以定義自己的參數匹配器 SQLEquals。
要定義新的參數匹配器,需要實現 org.easymock.IArgumentMatcher 接口。其中,matches(Object actual) 方法應當實現輸入值和預期值的匹配邏輯,而在 appendTo(StringBuffer buffer) 方法中,你可以添加當匹配失敗時需要顯示的信息。以下是 SQLEquals 實現的部分代碼(完整的代碼可以在 src.zip 中找到):
清單5:自定義參數匹配器SQLEquals
public class SQLEquals implements IArgumentMatcher {
private String expectedSQL = null;
public SQLEquals(String expectedSQL) {
this.expectedSQL = 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;
}
}
在實現了 IArgumentMatcher 接口之后,我們需要寫一個靜態(tài)方法將它包裝一下。這個靜態(tài)方法的實現需要將 SQLEquals 的一個對象通過 reportMatcher 方法報告給EasyMock:
清單6:自定義參數匹配器 SQLEquals 靜態(tài)方法
public static String sqlEquals(String in) {
reportMatcher(new SQLEquals(in));
return in;
}
這樣,我們自定義的 sqlEquals 匹配器可以使用了。我們可以將上例中的 executeQuery 方法設定修改如下:
mockStatement.executeQuery(sqlEquals("SELECT * FROM sales_order_table"));
expectLastCall().andStubReturn(mockResultSet);
在使用 executeQuery("select * from sales_order_table") 進行方法調用時,該預期行為將被匹配。
4.特殊的 Mock 對象類型
到目前為止,我們所創(chuàng)建的 Mock 對象都屬于 EasyMock 默認的 Mock 對象類型,它對預期方法的調用次序不敏感,對非預期的方法調用拋出 AssertionError。除了這種默認的 Mock 類型以外,EasyMock 還提供了一些特殊的 Mock 類型用于支持不同的需求。
Strick Mock 對象
如果 Mock 對象是通過 EasyMock.createMock() 或是 IMocksControl.createMock() 所創(chuàng)建的,那么在進行 verify 驗證時,方法的調用順序是不進行檢查的。如果要創(chuàng)建方法調用的先后次序敏感的 Mock 對象(Strick Mock),應該使用 EasyMock.createStrickMock() 來創(chuàng)建,例如:
ResultSet strickMockResultSet = createStrickMock(ResultSet.class);
類似于 createMock,我們同樣可以用 IMocksControl 實例來創(chuàng)建一個 Strick Mock 對象:
IMocksControl control = EasyMock.createStrictControl();
ResultSet strickMockResultSet = control.createMock(ResultSet.class);
Nice Mock 對象
使用 createMock() 創(chuàng)建的 Mock 對象對非預期的方法調用默認的行為是拋出 AssertionError,如果需要一個默認返回0,null 或 false 等"無效值"的 "Nice Mock" 對象,可以通過 EasyMock 類提供的 createNiceMock() 方法創(chuàng)建。類似的,你也可以用
5.EasyMock 的工作原理
EasyMock 是如何為一個特定的接口動態(tài)創(chuàng)建 Mock 對象,并記錄 Mock 對象預期行為的呢?其實,EasyMock 后臺處理的主要原理是利用 java.lang.reflect.Proxy 為指定的接口創(chuàng)建一個動態(tài)代理,這個動態(tài)代理,是我們在編碼中用到的 Mock 對象。EasyMock 還為這個動態(tài)代理提供了一個 InvocationHandler 接口的實現,這個實現類的主要功能是將動態(tài)代理的預期行為記錄在某個映射表中和在實際調用時從這個映射表中取出預期輸出。下圖是 EasyMock 中主要的功能類:
圖4:EasyMock主要功能類
和開發(fā)人員聯系緊密的是 EasyMock 類,這個類提供了 createMock、replay、verify 等方法以及所有預定義的參數匹配器。