如果需要在相對復(fù)雜的測試用例中使用多個 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 方法能創(chuàng)建一個接口 IMocksControl 的對象,該對象能創(chuàng)建并管理多個 Mock 對象。如果需要在測試中使用多個 Mock 對象,我們推薦您使用這一機制,因為它在多個 Mock 對象的管理上提供了相對便捷的方法。
如果您要模擬的是一個具體類而非接口,那么您需要下載擴展包 EasyMock Class Extension 2.2.2。在對具體類進行模擬時,您只要用 org.easymock.classextension.EasyMock 類中的靜態(tài)方法代替 org.easymock.EasyMock 類中的靜態(tài)方法即可。
設(shè)定 Mock 對象的預(yù)期行為和輸出
在一個完整的測試過程中,一個 Mock 對象將會經(jīng)歷兩個狀態(tài):Record 狀態(tài)和 Replay 狀態(tài)。Mock 對象一經(jīng)創(chuàng)建,它的狀態(tài)被置為 Record。在 Record 狀態(tài),用戶可以設(shè)定 Mock 對象的預(yù)期行為和輸出,這些對象行為被錄制下來,保存在 Mock 對象中。
添加 Mock 對象行為的過程通常可以分為以下3步:
對 Mock 對象的特定方法作出調(diào)用;
通過 org.easymock.EasyMock 提供的靜態(tài)方法 expectLastCall 獲取上一次方法調(diào)用所對應(yīng)的 IExpectationSetters 實例;
通過 IExpectationSetters 實例設(shè)定 Mock 對象的預(yù)期輸出。
設(shè)定預(yù)期返回值
Mock 對象的行為可以簡單的理解為 Mock 對象方法的調(diào)用和方法調(diào)用所產(chǎn)生的輸出。在 EasyMock 2.3 中,對 Mock 對象行為的添加和設(shè)置是通過接口 IExpectationSetters 來實現(xiàn)的。Mock 對象方法的調(diào)用可能產(chǎn)生兩種類型的輸出:(1)產(chǎn)生返回值;(2)拋出異常。接口 IExpectationSetters 提供了多種設(shè)定預(yù)期輸出的方法,其中和設(shè)定返回值相對應(yīng)的是 andReturn 方法:
IExpectationSetters<T> andReturn(T value);
我們?nèi)匀挥?ResultSet 接口的 Mock 對象為例,如果希望方法 mockResult.getString(1) 的返回值為 "My return value",那么你可以使用以下的語句:
mockResultSet.getString(1);
expectLastCall().andReturn("My return value");
以上的語句表示 mockResultSet 的 getString 方法被調(diào)用一次,這次調(diào)用的返回值是 "My return value"。有時,我們希望某個方法的調(diào)用總是返回一個相同的值,為了避免每次調(diào)用都為 Mock 對象的行為進行一次設(shè)定,我們可以用設(shè)置默認(rèn)返回值的方法:
void andStubReturn(Object value);
假設(shè)我們創(chuàng)建了 Statement 和 ResultSet 接口的 Mock 對象 mockStatement 和 mockResultSet,在測試過程中,我們希望 mockStatement 對象的 executeQuery 方法總是返回 mockResultSet,我們可以使用如下的語句
mockStatement.executeQuery("SELECT * FROM sales_order_table");
expectLastCall().andStubReturn(mockResultSet);
EasyMock 在對參數(shù)值進行匹配時,默認(rèn)采用 Object.equals() 方法。因此,如果我們以 "select * from sales_order_table" 作為參數(shù),預(yù)期方法將不會被調(diào)用。如果您希望上例中的 SQL 語句能不區(qū)分大小寫,可以用特殊的參數(shù)匹配器來解決這個問題,我們將在 "在 EasyMock 中使用參數(shù)匹配器" 一章對此進行說明。
設(shè)定預(yù)期異常拋出
對象行為的預(yù)期輸出除了可能是返回值外,還有可能是拋出異常。IExpectationSetters 提供了設(shè)定預(yù)期拋出異常的方法:
IExpectationSetters<T> andThrow(Throwable throwable);
和設(shè)定默認(rèn)返回值類似,IExpectationSetters 接口也提供了設(shè)定拋出默認(rèn)異常的函數(shù):
void andStubThrow(Throwable throwable);
設(shè)定預(yù)期方法調(diào)用次數(shù)
通過以上的函數(shù),您可以對 Mock 對象特定行為的預(yù)期輸出進行設(shè)定。除了對預(yù)期輸出進行設(shè)定,IExpectationSetters 接口還允許用戶對方法的調(diào)用次數(shù)作出限制。在 IExpectationSetters 所提供的這一類方法中,常用的一種是 times 方法:
IExpectationSetters<T>times(int count);
該方法可以 Mock 對象方法的調(diào)用次數(shù)進行確切的設(shè)定。假設(shè)我們希望 mockResultSet 的 getString 方法在測試過程中被調(diào)用3次,期間的返回值都是 "My return value",我們可以用如下語句:
mockResultSet.getString(1);
expectLastCall().andReturn("My return value").times(3);
注意到 andReturn 和 andThrow 方法的返回值依然是一個 IExpectationSetters 實例,因此我們可以在此基礎(chǔ)上繼續(xù)調(diào)用 times 方法。
除了設(shè)定確定的調(diào)用次數(shù),IExpectationSetters 還提供了另外幾種設(shè)定非準(zhǔn)確調(diào)用次數(shù)的方法:
times(int minTimes, int maxTimes):該方法少被調(diào)用 minTimes 次,多被調(diào)用 maxTimes 次。
atLeastOnce():該方法至少被調(diào)用一次。
anyTimes():該方法可以被調(diào)用任意次。
某些方法的返回值類型是 void,對于這一類方法,我們無需設(shè)定返回值,只要設(shè)置調(diào)用次數(shù)可以了。以 ResultSet 接口的 close 方法為例,假設(shè)在測試過程中,該方法被調(diào)用3至5次:
mockResultSet.close();
expectLastCall().times(3, 5);
為了簡化書寫,EasyMock 還提供了另一種設(shè)定 Mock 對象行為的語句模式。對于上例,您還可以將它寫成:
expect(mockResult.close()).times(3, 5);
這個語句和上例中的語句功能是完全相同的。
將 Mock 對象切換到 Replay 狀態(tài)
在生成 Mock 對象和設(shè)定 Mock 對象行為兩個階段,Mock 對象的狀態(tài)都是 Record 。在這個階段,Mock 對象會記錄用戶對預(yù)期行為和輸出的設(shè)定。
在使用 Mock 對象進行實際的測試前,我們需要將 Mock 對象的狀態(tài)切換為 Replay。在 Replay 狀態(tài),Mock 對象能夠根據(jù)設(shè)定對特定的方法調(diào)用作出預(yù)期的響應(yīng)。將 Mock 對象切換成 Replay 狀態(tài)有兩種方式,您需要根據(jù) Mock 對象的生成方式進行選擇。如果 Mock 對象是通過 org.easymock.EasyMock 類提供的靜態(tài)方法 createMock 生成的(第1節(jié)中介紹的第一種 Mock 對象生成方法),那么 EasyMock 類提供了相應(yīng)的 replay 方法用于將 Mock 對象切換為 Replay 狀態(tài):
replay(mockResultSet);
如果 Mock 對象是通過 IMocksControl 接口提供的 createMock 方法生成的(第1節(jié)中介紹的第二種Mock對象生成方法),那么您依舊可以通過 IMocksControl 接口對它所創(chuàng)建的所有 Mock 對象進行切換:
control.replay();
以上的語句能將在第1節(jié)中生成的 mockConnection、mockStatement 和 mockResultSet 等3個 Mock 對象都切換成 Replay 狀態(tài)。
調(diào)用 Mock 對象方法進行單元測試
為了更好的說明 EasyMock 的功能,我們引入 src.zip 中的示例來解釋 Mock 對象在實際測試階段的作用。其中所有的示例代碼都可以在 src.zip 中找到。如果您使用的 IDE 是 Eclipse,在導(dǎo)入 src.zip 之后您可以看到 Workspace 中增加的 project(如下圖所示)。
圖2:導(dǎo)入 src.zip 后的 Workspace