EasyMock 简介
來(lái)源:https://www.ibm.com/developerworks/cn/opensource/os-cn-easymock/
使用注意:
a、靜態(tài)方法( static 修飾)無(wú)法模擬。
1、使用 EasyMock 進(jìn)行單元測(cè)試
通過(guò) EasyMock,我們可以為指定的接口動(dòng)態(tài)的創(chuàng)建 Mock 對(duì)象,并利用 Mock 對(duì)象來(lái)模擬協(xié)同模塊或是領(lǐng)域?qū)ο?#xff0c;從而使單元測(cè)試順利進(jìn)行。這個(gè)過(guò)程大致可以劃分為以下幾個(gè)步驟:
?? ???使用 EasyMock 生成 Mock 對(duì)象;
?? ???設(shè)定 Mock 對(duì)象的預(yù)期行為和輸出;
?? ???將 Mock 對(duì)象切換到 Replay 狀態(tài);
?? ???調(diào)用 Mock 對(duì)象方法進(jìn)行單元測(cè)試;
?? ???對(duì) Mock 對(duì)象的行為進(jìn)行驗(yàn)證。
接下來(lái),我們將對(duì)以上的幾個(gè)步驟逐一進(jìn)行說(shuō)明。除了以上的基本步驟外,EasyMock 還對(duì)特殊的 Mock 對(duì)象類(lèi)型、特定的參數(shù)匹配方式等功能提供了支持,我們將在之后的章節(jié)中進(jìn)行說(shuō)明。
使用 EasyMock 生成 Mock 對(duì)象
根據(jù)指定的接口或類(lèi),EasyMock 能夠動(dòng)態(tài)的創(chuàng)建 Mock 對(duì)象(EasyMock 默認(rèn)只支持為接口生成 Mock 對(duì)象,如果需要為類(lèi)生成 Mock 對(duì)象,在 EasyMock 的主頁(yè)上有擴(kuò)展包可以實(shí)現(xiàn)此功能),我們以 ResultSet 接口為例說(shuō)明EasyMock的功能。java.sql.ResultSet 是每一個(gè) Java 開(kāi)發(fā)人員都非常熟悉的接口:
清單1:ResultSet 接口
我們可以使用 EasyMock 動(dòng)態(tài)構(gòu)建 ResultSet 接口的 Mock 對(duì)象來(lái)解決這個(gè)問(wèn)題。一些簡(jiǎn)單的測(cè)試用例只需要一個(gè) Mock 對(duì)象,這時(shí),我們可以用以下的方法來(lái)創(chuàng)建 Mock 對(duì)象:
如果需要在相對(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 類(lèi)的 createControl 方法能創(chuàng)建一個(gè)接口 IMocksControl 的對(duì)象,該對(duì)象能創(chuàng)建并管理多個(gè) Mock 對(duì)象。如果需要在測(cè)試中使用多個(gè) Mock 對(duì)象,我們推薦您使用這一機(jī)制,因?yàn)樗诙鄠€(gè) Mock 對(duì)象的管理上提供了相對(duì)便捷的方法。
如果您要模擬的是一個(gè)具體類(lèi)而非接口,那么您需要下載擴(kuò)展包 EasyMock Class Extension 2.2.2。在對(duì)具體類(lèi)進(jìn)行模擬時(shí),您只要用 org.easymock.classextension.EasyMock 類(lèi)中的靜態(tài)方法代替 org.easymock.EasyMock 類(lèi)中的靜態(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),用戶(hù)可以設(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)生兩種類(lèi)型的輸出:(1)產(chǎn)生返回值;(2)拋出異常。接口 IExpectationSetters 提供了多種設(shè)定預(yù)期輸出的方法,其中和設(shè)定返回值相對(duì)應(yīng)的是 andReturn 方法:
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ū)分大小寫(xiě),可以用特殊的參數(shù)匹配器來(lái)解決這個(gè)問(wèn)題,我們將在 "在 EasyMock 中使用參數(shù)匹配器" 一章對(duì)此進(jìn)行說(shuō)明。
設(shè)定預(yù)期異常拋出
對(duì)象行為的預(yù)期輸出除了可能是返回值外,還有可能是拋出異常。IExpectationSetters 提供了設(shè)定預(yù)期拋出異常的方法:
void andStubThrow(Throwable throwable);
通過(guò)以上的函數(shù),您可以對(duì) Mock 對(duì)象特定行為的預(yù)期輸出進(jìn)行設(shè)定。除了對(duì)預(yù)期輸出進(jìn)行設(shè)定,IExpectationSetters 接口還允許用戶(hù)對(duì)方法的調(diào)用次數(shù)作出限制。在 IExpectationSetters 所提供的這一類(lèi)方法中,常用的一種是 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)用任意次。
某些方法的返回值類(lèi)型是 void,對(duì)于這一類(lèi)方法,我們無(wú)需設(shè)定返回值,只要設(shè)置調(diào)用次數(shù)就可以了。以 ResultSet 接口的 close 方法為例,假設(shè)在測(cè)試過(guò)程中,該方法被調(diào)用3至5次:
expect(mockResult.close()).times(3, 5);
將 Mock 對(duì)象切換到 Replay 狀態(tài)
在生成 Mock 對(duì)象和設(shè)定 Mock 對(duì)象行為兩個(gè)階段,Mock 對(duì)象的狀態(tài)都是 Record 。在這個(gè)階段,Mock 對(duì)象會(huì)記錄用戶(hù)對(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 類(lèi)提供的靜態(tài)方法 createMock 生成的(第1節(jié)中介紹的第一種 Mock 對(duì)象生成方法),那么 EasyMock 類(lèi)提供了相應(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();
下面是示例代碼中的一個(gè)接口 SalesOrder,它的實(shí)現(xiàn)類(lèi) SalesOrderImpl 的主要功能是從數(shù)據(jù)庫(kù)中讀取一個(gè) Sales Order 的 Region 和 Total Price,并根據(jù)讀取的數(shù)據(jù)計(jì)算該 Sales Order 的 Price Level(完整的實(shí)現(xiàn)代碼都可以在 src.zip 中找到):
清單2:SalesOrder 接口
public interface SalesOrder {……public void loadDataFromDB(ResultSet resultSet) throws SQLException; public String getPriceLevel(); }其實(shí)現(xiàn)類(lèi) SalesOrderImpl 中對(duì) loadDataFromDB 的實(shí)現(xiàn)如下:
清單3:SalesOrderImpl 實(shí)現(xiàn) public class SalesOrderImpl implements SalesOrder {......public void loadDataFromDB(ResultSet resultSet) throws SQLException{orderNumber = resultSet.getString(1);region = resultSet.getString(2);totalPrice = resultSet.getDouble(3);}...... }方法 loadDataFromDB 讀取了 ResultSet 對(duì)象包含的數(shù)據(jù)。當(dāng)我們將之前定義的 Mock 對(duì)象調(diào)整為 Replay 狀態(tài),并將該對(duì)象作為參數(shù)傳入,那么 Mock 對(duì)象的方法將會(huì)返回預(yù)先定義的預(yù)期返回值。完整的 TestCase 如下:
清單4:完整的TestCase
public class SalesOrderTestCase extends TestCase {public void testSalesOrder() {IMocksControl control = EasyMock.createControl();......ResultSet mockResultSet = control.createMock(ResultSet.class);try {......mockResultSet.next();expectLastCall().andReturn(true).times(3);expectLastCall().andReturn(false).times(1);mockResultSet.getString(1);expectLastCall().andReturn("DEMO_ORDER_001").times(1);expectLastCall().andReturn("DEMO_ORDER_002").times(1);expectLastCall().andReturn("DEMO_ORDER_003").times(1);mockResultSet.getString(2);expectLastCall().andReturn("Asia Pacific").times(1);expectLastCall().andReturn("Europe").times(1);expectLastCall().andReturn("America").times(1);mockResultSet.getDouble(3);expectLastCall().andReturn(350.0).times(1);expectLastCall().andReturn(1350.0).times(1);expectLastCall().andReturn(5350.0).times(1);control.replay();......int i = 0;String[] priceLevels = { "Level_A", "Level_C", "Level_E" };while (mockResultSet.next()) {SalesOrder order = new SalesOrderImpl();order.loadDataFromDB(mockResultSet);assertEquals(order.getPriceLevel(), priceLevels[i]);i++;}control.verify();} catch (Exception e) {e.printStackTrace();}} }在這個(gè)示例中,我們首先創(chuàng)建了 ResultSet 的 Mock 對(duì)象 moResultSet,并記錄該 Mock 對(duì)象的預(yù)期行為。之后我們調(diào)用了 control.replay(),將 Mock 對(duì)象的狀態(tài)置為 Replay 狀態(tài)。在實(shí)際的測(cè)試階段,Sales Order 對(duì)象的 loadDataFromDB 方法調(diào)用了 mockResultSet 對(duì)象的 getString 和 getDouble 方法讀取 mockResultSet 中的數(shù)據(jù)。Sales Order 對(duì)象根據(jù)讀取的數(shù)據(jù)計(jì)算出 Price Level,并和預(yù)期輸出進(jìn)行比較。對(duì) Mock 對(duì)象的行為進(jìn)行驗(yàn)證
在利用 Mock 對(duì)象進(jìn)行實(shí)際的測(cè)試過(guò)程之后,我們還有一件事情沒(méi)有做:對(duì) Mock 對(duì)象的方法調(diào)用的次數(shù)進(jìn)行驗(yàn)證。
為了驗(yàn)證指定的方法調(diào)用真的完成了,我們需要調(diào)用 verify 方法進(jìn)行驗(yàn)證。和 replay 方法類(lèi)似,您需要根據(jù) Mock 對(duì)象的生成方式來(lái)選用不同的驗(yàn)證方式。如果 Mock 對(duì)象是由 org.easymock.EasyMock 類(lèi)提供的 createMock 靜態(tài)方法生成的,那么我們同樣采用 EasyMock 類(lèi)的靜態(tài)方法 verify 進(jìn)行驗(yàn)證:
control.verify();
Mock 對(duì)象的重用
為了避免生成過(guò)多的 Mock 對(duì)象,EasyMock 允許對(duì)原有 Mock 對(duì)象進(jìn)行重用。要對(duì) Mock 對(duì)象重新初始化,我們可以采用 reset 方法。和 replay 和 verify 方法類(lèi)似,EasyMock 提供了兩種 reset 方式:(1)如果 Mock 對(duì)象是由 org.easymock.EasyMock 類(lèi)中的靜態(tài)方法 createMock 生成的,那么該 Mock 對(duì)象的可以用 EasyMock 類(lèi)的靜態(tài)方法 reset 重新初始化;(2)如果 Mock 方法是由 IMocksControl 實(shí)例的 createMock 方法生成的,那么該 IMocksControl 實(shí)例方法 reset 的調(diào)用將會(huì)把所有該實(shí)例創(chuàng)建的 Mock 對(duì)象重新初始化。在重新初始化之后,Mock 對(duì)象的狀態(tài)將被置為 Record 狀態(tài)。
總結(jié)
以上是生活随笔為你收集整理的EasyMock 简介的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Java测试驱动开发--总结
- 下一篇: Replace Parameter wi