最近開始玩WEB相關的技術,在這里先極度的鄙視一下那些這要功能而不注重TestCase的菜鳥們,
好了,不把工作上的東東帶到這里來。
單元測試是Agile 極力推薦的測試驅動開發模式,是保證軟件質量的重要方法。盡管如此,對許多
類的單元測試仍然是極其困難的,例如,對數據庫操作的類進行測試,如果不準備好數據庫環境以
及相關測試數據,是很難進行單元測試的;再例如,對需要運行在容器內的Servlet 或EJB 組件,脫
離了容器也難于測試。
幸運的是,Mock Object 可以用來模擬一些我們需要的類,這些對象被稱之為模仿對象,在單元
測試中它們特別有價值。
Mock Object 用于模仿真實對象的方法調用,從而使得測試不需要真正的依賴對象。Mock Object
只為某個特定的測試用例的場景提供剛好滿足需要的最少功能。它們還可以模擬錯誤的條件,例如
拋出指定的異常等。
目前,有許多可用的Mock 類庫可供我們選擇。一些Mock 庫提供了常見的模仿對象,例如:
HttpServletRequest,而另一些Mock 庫則提供了動態生成模仿對象的功能,本文將討論使用EasyMock
動態生成模仿對象以便應用于單元測試。
到目前為止,EasyMock 提供了1.2 版本和2.0 版本,2.0 版本僅支持Java SE 5.0,本例中,我們
選擇EasyMock 1.2 for Java 1.3 版本進行測試,可以從http://www.easymock.org 下載合適的版本。
我們首先來看一個用戶驗證的LoginServlet 類:
-
-
-
- package com.javaeedev.test.mock;
- import java.io.*;
- import javax.servlet.*;
- import javax.servlet.http.*;
-
- 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");
-
- 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();
-
- request.getParameter("username");
- mc.setReturnValue("admin", 1);
- request.getParameter("password");
- mc.setReturnValue("1234", 1);
-
- mc.replay();
-
- LoginServlet servlet = new LoginServlet();
- try {
- servlet.doPost(request, null);
- fail("Not caught exception!");
- }catch(RuntimeException re) {
- assertEquals("Login failed.", re.getMessage());
- }
-
- 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");
- mc.setReturnValue("admin", 1);
- request.getParameter("password");
- mc.setReturnValue("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 ...
完整的LoginServletTest 代碼如下:
-
-
-
- package com.javaeedev.test.mock;
-
- import javax.servlet.*;
- import javax.servlet.http.*;
- import org.easymock.*;
- import junit.framework.TestCase;
-
- public class LoginServletTest extends TestCase {
- public void testLoginFailed() throws Exception {
- MockControl mc = MockControl.createControl(HttpServletRequest.class);
- HttpServletRequest request = (HttpServletRequest)mc.getMock();
-
- request.getParameter("username");
- mc.setReturnValue("admin", 1);
- request.getParameter("password");
- mc.setReturnValue("1234", 1);
-
- mc.replay();
-
- LoginServlet servlet = new LoginServlet();
- try {
- servlet.doPost(request, null);
- fail("Not caught exception!");
- }catch(RuntimeException re) {
- assertEquals("Login failed.", re.getMessage());
- }
-
- mc.verify();
- }
- public void testLoginOK() throws Exception {
-
- 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();
-
- 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();
-
- LoginServlet servlet = new LoginServlet() {
- public ServletContext getServletContext() {
- return contextObj;
- }
- };
- servlet.doPost(requestObj, null);
-
- requestCtrl.verify();
- contextCtrl.verify();
- dispatcherCtrl.verify();
- }
- }
posted on 2011-04-28 23:39
Daniel 閱讀(513)
評論(0) 編輯 收藏 所屬分類:
Web Test Framework