xp開發的一個利器--EasyMock。
EasyMock是一種
動態生成模仿對象以便應用于單元測試的工具,有了它可擺脫容器進行單元測試了。
它的使用很簡單,下面一個簡單登陸驗證的例子:
public
?
class
?LoginServlet?
extends
?HttpServlet?{
protected
?
void
?doPost(HttpServletRequest?request,?HttpServletResponse?response)?
throws
?ServletException,?IOException?{
String?username?
=
?request.getParameter(
"
username
"
);
String?password?
=
?request.getParameter(
"
password
"
);
//
?check?username?&?password:
if
(
"
admin
"
.equals(username)?
&&
?
"
123456
"
.equals(password))?{
?????ServletContext?context?
=
?getServletContext();
?????RequestDispatcher?dispatcher?
=
?context.getNamedDispatcher(
"
dispatcher
"
);
?????dispatcher.forward(request,?response);
}
else
?{
?????
throw
?
new
?RuntimeException(
"
Login?failed.
"
);
}
}
}
這個
Servlet
實現簡單的用戶驗證的功能,若用戶名和口令匹配“
admin
”和“
123456
”,則請求被轉發到指定的
dispatcher
上,否則,直接拋出
RuntimeException
。
為了測試
doPost()
方法,我們需要模擬
HttpServletRequest
,
ServletContext
和
RequestDispatcher
對象,以便脫離
J2EE
容器來測試這個
Servlet
。
我們建立
TestCase
,名為
LoginServletTest
:
public
?
class
?LoginServletTest?
extends
?TestCase?{
}
我們首先測試當用戶名和口令驗證失敗的情形,演示如何使用EasyMock來模擬HttpServletRequest對象:
public
?
void
?testLoginFailed()?
throws
?Exception?{
MockControl?mc?
=
?MockControl.createControl(HttpServletRequest.
class
);
HttpServletRequest?request?
=
?(HttpServletRequest)mc.getMock();
//
?set?Mock?Object?behavior:
request.getParameter(
"
username
"
);
mc.setReturnValue(
"
admin
"
,?
1
);
request.getParameter(
"
password
"
);
mc.setReturnValue(
"
1234
"
,?
1
);
//
?ok,?all?behaviors?are?set!
mc.replay();
//
?now?start?test:
LoginServlet?servlet?
=
?
new
?LoginServlet();
try
?{
servlet.doPost(request,?
null
);
fail(
"
Not?caught?exception!
"
);
}
catch
(RuntimeException?re)?{
assertEquals(
"
Login?failed.
"
,?re.getMessage());
}
//
?verify:
mc.verify();
}
仔細觀察測試代碼,使用
EasyMock
來創建一個
Mock
對象需要首先創建一個
MockControl
:
MockControl?mc?
=
?MockControl.createControl(HttpServletRequest.
class
);
然后,即可獲得
MockControl
創建的
Mock
對象:
HttpServletRequest?request?
=
?(HttpServletRequest)mc.getMock();
下一步,我們需要“錄制”
Mock
對象的預期行為。在
LoginServlet
中,先后調用了
request.getParameter("username")
和
request.getParameter("password")
兩個方法,因此,需要在
MockControl
中設置這兩次調用后的指定返回值。我們期望返回的值為“
admin
”和“
1234
”:
request.getParameter(
"
username
"
);?
//
?期望下面的測試將調用此方法,參數為"username"
mc.setReturnValue(
"
admin
"
,?
1
);?
//
?期望返回值為"admin",僅調用1次
request.getParameter(
"
password
"
);?
//
?期望下面的測試將調用此方法,參數為"?password"
mc.setReturnValue(
"
1234
"
,?
1
);?
//
?期望返回值為"1234",僅調用1次
緊接著,調用
mc.replay()
,表示
Mock
對象“錄制”完畢,可以開始按照我們設定的方式運行,我們對
LoginServlet
進行測試,并預期會產生一個
RuntimeException
:
LoginServlet?servlet?
=
?
new
?LoginServlet();
try
?{
servlet.doPost(request,?
null
);
fail(
"
Not?caught?exception!
"
);
}
catch
(RuntimeException?re)?{
assertEquals(
"
Login?failed.
"
,?re.getMessage());
}
由于本次測試的目的是檢查當用戶名和口令驗證失敗后,
LoginServlet
是否會拋出
RuntimeException
,因此,
response
對象對測試沒有影響,我們不需要模擬它,僅僅傳入
null
即可。
最后,調用
mc.verify()
檢查
Mock
對象是否按照預期的方法調用正常運行了。
運行
JUnit
,測試通過!表示我們的
Mock
對象正確工作了!
? 下一步,我們來測試當用戶名和口令匹配時,LoginServlet應當把請求轉發給指定的RequestDispatcher。在這個測試用例中,我們除了需要HttpServletRequest Mock對象外,還需要模擬ServletContext和RequestDispatcher對象:
MockControl?requestCtrl?
=
?MockControl.createControl(HttpServletRequest.
class
);
HttpServletRequest?requestObj?
=
?(HttpServletRequest)requestCtrl.getMock();
MockControl?contextCtrl?
=
?MockControl.createControl(ServletContext.
class
);
final
?ServletContext?contextObj?
=
?(ServletContext)contextCtrl.getMock();
MockControl?dispatcherCtrl?
=
?MockControl.createControl(RequestDispatcher.
class
);
RequestDispatcher?dispatcherObj?
=
?(RequestDispatcher)dispatcherCtrl.getMock();
按照doPost()的語句順序,我們設定Mock對象指定的行為:
requestObj.getParameter(
"
username
"
);
requestCtrl.setReturnValue(
"
admin
"
,?
1
);
requestObj.getParameter(
"
password
"
);
requestCtrl.setReturnValue(
"
123456
"
,?
1
);
contextObj.getNamedDispatcher(
"
dispatcher
"
);
contextCtrl.setReturnValue(dispatcherObj,?
1
);
dispatcherObj.forward(requestObj,?
null
);
dispatcherCtrl.setVoidCallable(
1
);
requestCtrl.replay();
contextCtrl.replay();
dispatcherCtrl.replay();
然后,測試
doPost()
方法,這里,為了讓
getServletContext()
方法返回我們創建的
ServletContext Mock
對象,我們定義一個匿名類并覆寫
getServletContext()
方法:
LoginServlet?servlet?
=
?
new
?LoginServlet()?{
public
?ServletContext?getServletContext()?{
return
?contextObj;
}
};
servlet.doPost(requestObj,?
null
);
最后,檢查所有Mock對象的狀態:
requestCtrl.verify();
contextCtrl.verify();
dispatcherCtrl.verify();
運行
JUnit
,測試通過!
倘若
LoginServlet
的代碼有誤,例如,將
context.getNamedDispatcher("dispatcher")
誤寫為
context.getNamedDispatcher("dispatcher2")
,則測試失敗,
JUnit
報告:
junit.framework.AssertionFailedError:
Unexpected method call getNamedDispatcher("dispatcher2"):
getNamedDispatcher("dispatcher2"): expected: 0, actual: 1
getNamedDispatcher("dispatcher"): expected: 1, actual: 0
at ...
總結:
雖然
EasyMock
可以用來模仿依賴對象,但是,它只能動態模仿接口,無法模仿具體類。這一限制正好要求我們遵循“針對接口編程”的原則:如果不針對接口,則測試難于進行。應當把單元測試看作是運行時代碼的最好運用,如果代碼在單元測試中難于應用,則它在真實環境中也將難于應用。總之,創建盡可能容易測試的代碼就是創建高質量的代碼。
? 現在,easymock可以模仿類了,以下是在springside摘的:
//
設定BookManager?MockObject
????????bookManagerMockControl?
=
?MockClassControl.createControl(BookManager.
class
);
????????bookManagerMock?
=
?(BookManager)?bookManagerMockControl.getMock();
????????controller.setBookManager(bookManagerMock);
???????
//
錄制getAllBook()和getCategorys方法的期望值
????????bookManagerMock.getAllBook();
????????bookManagerMockControl.setReturnValue(
new
?ArrayList());
????????bookManagerMockControl.replay();
????????
//
執行操作
????????mv?
=
?controller.handleRequest(request,?response);
//
驗證結果?????????
????????assertModelAttributeAvailable(mv,?
"
books
"
);