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