Mock 對象能夠模擬領域對象的部分行為,并且能夠檢驗運行結果是否和預期的一致。領域類將通過與 Mock 對象的交互,來獲得一個獨立的測試環(huán)境(引自《 精通 Spring——Java 輕量級架構開發(fā)實踐 》。
在模仿對象中,我們定義了四個概念:
1 )目標對象:正在測試的對象
2 )合作者對象:由目標對象創(chuàng)建或獲取的對象
3 )模仿對象:遵循模仿對象模式的合作者的子類(或實現(xiàn))
4 )特殊化對象:覆蓋創(chuàng)建方法以返回模仿對象而不是合作者的目標的子類
一般來說,應用模仿對象的過程如下:
1 )創(chuàng)建模仿對象的實例
2 )設置模仿對象中的狀態(tài)和期望值
3 )將模仿對象作為參數(shù)來調(diào)用域代碼
4 )驗證模仿對象中的一致性
那么,我們應該如何以及在哪里使用 Mock 對象呢?一般來說,對于目標對象中的合作者對象,在測試時如果其狀態(tài)或行為的實現(xiàn)嚴重地依賴外部資源(比如數(shù)據(jù)持久化中的 DAO ,比如負責發(fā)送電子郵件的類),或者團隊并行開發(fā)時,目標對象的合作者對象并沒有實現(xiàn)(比如 J2EE 中,橫向分工時,負責 Action 的調(diào)用 Service ,負責 Service 調(diào)用 DAO 時,相應的 Service 及 DAO 沒有實現(xiàn)),這時我們就需要模仿這些類。其實,在做 J2EE 時,傳統(tǒng)的 N 層架構中,我們都是面向接口編程的,我們定義了 DAO 接口,我們定義了 Service 接口,這樣做的優(yōu)點就是我們在測試時可以構造實現(xiàn)接口的 Mock 類。這里不得不提依賴注入,通過依賴注入,我們才能在測試時 set Mock 對象。這也說明,為了方便測試,我們不得不一步一步 重構代碼,而模式就在重構中自然地產(chǎn)生了。
對于 Mock 對象,我們可以根據(jù) 合作者接口(或者是類) 實現(xiàn)具體的 Mock 類,這樣的 Mock 類實際上是 Stub 。有些情況下, Stub 是必要的。但對于諸如 DAO 、 Service ,我們只關心在給定參數(shù)的情況下,調(diào)用的方法能夠返回預期的值,我們根本不關心其內(nèi)部實現(xiàn),這時候如果使用 Stub 的話就會產(chǎn)生不必要的代碼。我們需要的就是能 動態(tài)地生成 Mock 對象而不需要編寫它們的工具, EasyMock 就是這樣的工具。
EasyMock 是一個 Mock 對象的類庫,現(xiàn)在的版本是 2.0 ,這個版本只支持 Mock 接口,如果需要 Mock 類,需要下載它的擴展包。
下面通過一個具體的例子說明一下 Mock 對象的使用,我寫的例子就是測試 Service 類中的一個方法, Mock 的對象是 DAO 。
首先是一個簡單的實體 bean Account:
package easymocktest.domain;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;


public class Account {

private Long id;
private String name;
private String pwd;

public Long getId() {
return id;
}
public void setId(Long id) {
this .id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this .name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this .pwd = pwd;
}
/**
* @see java.lang.Object#toString()
*/
public String toString() {
return new ToStringBuilder( this , ToStringStyle.MULTI_LINE_STYLE)
.append( " name " , this .name).append( " pwd " , this .pwd).append( " id " ,
this .id).toString();
}
}
一個只含一個方法的 DAO AccountDAO:
package easymocktest.dao;

import easymocktest.domain. * ;

public interface AccountDAO {
public Account getByNameAndPwd(String name,String pwd);
}
一個同樣只含一個方法的 AccountService 接口:
package easymocktest.service;

import easymocktest.domain. * ;

public interface AccountService {
public Account getAccount(String name,String pwd);
}
與 AccountService 相應的實現(xiàn)類:
package easymocktest.service.impl;

import easymocktest.service.AccountService;
import easymocktest.dao. * ;
import easymocktest.domain.Account;

public class AccountServiceImpl implements AccountService {

private AccountDAO accountDAO;


public AccountDAO getAccountDAO() {
return accountDAO;
}
public void setAccountDAO(AccountDAO accountDAO) {
this .accountDAO = accountDAO;
}
public Account getAccount(String name, String pwd) {
return this .accountDAO.getByNameAndPwd(name, pwd);
}
}
這里我沒有實現(xiàn) AccountDAO 接口,對于 Mock 測試來說,這也是不需要的。下面就是 AccountServiceImpl 的測試類 AccountServiceTest :
package easymocktest.service;

import junit.framework. * ;
import easymocktest.dao. * ;
import easymocktest.domain. * ;
import easymocktest.service.impl. * ;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.reset;
import static org.easymock.EasyMock.verify;
import static org.easymock.EasyMock.expect;

public class AccountServiceTest extends TestCase {

private AccountDAO accountDAOMock;
private AccountServiceImpl accountService;
@Override

protected void setUp() throws Exception {
accountDAOMock = createMock(AccountDAO. class );
accountService = new AccountServiceImpl();
accountService.setAccountDAO(accountDAOMock);
}

public void testGetAccount() {
String name = " kafka " ;
String pwd = " 0102 " ;
Account a = new Account();
a.setName(name);
a.setPwd(pwd);
a.setId( new Long( 10 ));
reset(accountDAOMock); // (a)
expect(accountDAOMock.getByNameAndPwd(name, pwd)).andReturn(a); // (b)
replay(accountDAOMock); // (c)
Account b = accountService.getAccount(name, pwd);
assertEquals(a, b);
verify(accountDAOMock); // (d)
}
}
下面簡要的說明一下 Mock 對象的工作過程:
1 )在 setUp() 中,通過 “accountDAOMock = createMock(AccountDAO.class);” (這里使用了 java5 中的靜態(tài)導入),創(chuàng)建 AccountDAO 的 Mock 對象,由于 EasyMock 采用了范型技術,故創(chuàng)建的 Mock 對象不需要強制類型轉換。然后通過 “accountService.setAccountDAO(accountDAOMock);” 設置目標對象的合作者對象。
2 )對于測試方法 “testGetAccount()” , (a) 處的 reset() 方法是將 Mock 對象復位,也就是重新設置 Mock 對象的狀態(tài)和行為。由于此處是第一次調(diào)用 Mock 對象,可以不必使用 reset() 方法。
3 ) (b) 處 expect() 是錄制 Mock 對象方法的調(diào)用,其參數(shù)就是 Mock 對象的方法,其中如果調(diào)用的方法有返回值,要通過 andReturn() 方法設置預期的返回值。
4 ) (c) 處的 replay() 是結束錄制過程。 在調(diào)用 replay() 方法之前的狀態(tài), EashMock 稱之為 “record 狀態(tài) ” 。該狀態(tài)下, Mock 對象不具備行為(即模擬接口的實現(xiàn)),它僅僅記錄方法的調(diào)用。在調(diào)用 replay() 后,它才以 Mock 對象預期的行為進行工作,檢查預期的方法調(diào)用是否真的完成。
5 ) (d) 處的 verify() 是用于在錄制和回放兩個步驟完成之后進行預期和實際結果的檢查。這里就是檢查 accountDAOMock 是否如預期一樣調(diào)用了 getByNameAndPwd 方法。
對于上面的舉例,它可能并不具有實際的價值,這里我只想拋磚引玉。在 N 層架構的 Java 程序中, Mock 對象在單元測試中正發(fā)揮著越來越重要的作用。我現(xiàn)在看到的是,在 Service 層與 Web 層, Mock 對象能很好的被應用。有人覺得在 Persistence 層也應該使用 Mock 對象,但就像我們所知道的,在使用 Hibernate 、 Ibatis 等 ORM 工具的情況下,我們的 Persistence 層的測試主要測試的就是那些配置文件、查詢語句等(實際上是集成測試),如果還 Mock 的話,就失去了測試的意義。
對于 Mock 的更多的信息,你可以訪問Mock Objects,在本文寫作的過程中,參考了使用模仿對象進行單元測試
等文章,一并感謝。