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