小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

使用EasyMock使單元測試更加容易

 duduwolf 2006-03-01
使用EasyMock使單元測試更加容易
作者:xuefeng    文章來源:本站原創(chuàng)    點擊數(shù):61    更新時間:2006-2-25

單元測試是XP極力推薦的測試驅(qū)動開發(fā)模式,是保證軟件質(zhì)量的重要方法。盡管如此,對許多類的單元測試仍然是極其困難的,例如,對數(shù)據(jù)庫操作的類進行測試,如果不準備好數(shù)據(jù)庫環(huán)境以及相關(guān)測試數(shù)據(jù),是很難進行單元測試的;再例如,對需要運行在容器內(nèi)的Servlet或EJB組件,脫離了容器也難于測試。

幸運的是,Mock Object可以用來模擬一些我們需要的類,這些對象被稱之為模仿對象,在單元測試中它們特別有價值。

Mock Object用于模仿真實對象的方法調(diào)用,從而使得測試不需要真正的依賴對象。Mock Object只為某個特定的測試用例的場景提供剛好滿足需要的最少功能。它們還可以模仿錯誤的條件,例如拋出指定的異常等。

目前,有許多可用的Mock類庫可供我們選擇。一些Mock庫提供了常見的模仿對象,例如:HttpServletRequest,而另一些Mock庫則提供了動態(tài)生成模仿對象的功能,本文將討論使用EasyMock動態(tài)生成模仿對象以便應(yīng)用于單元測試。

到目前為止,EasyMock提供了1.2版本和2.0版本,2.0版本僅支持Java SE 5.0,本例中,我們選擇EasyMock 1.2 for Java 1.3版本進行測試,可以從http://www.下載合適的版本。
我們首先來看一個用戶驗證的LoginServlet類:

/**
 * LoginServlet.java
 * Author: Liao Xue Feng,
www.
 */
package com.crackj2ee.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");
        // 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實現(xiàn)簡單的用戶驗證的功能,若用戶名和口令匹配“admin”和“123456”,則請求被轉(zhuǎn)發(fā)到指定的dispatcher上,否則,直接拋出RuntimeException。

為了測試doPost()方法,我們需要模擬HttpServletRequest,ServletContext和RequestDispatcher對象,以便脫離J2EE容器來測試這個Servlet。
我們建立TestCase,名為LoginServletTest:

public class LoginServletTest extends TestCase {
}

我們首先測試當(dāng)用戶名和口令驗證失敗的情形,演示如何使用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來創(chuàng)建一個Mock對象需要首先創(chuàng)建一個MockControl:

    MockControl mc = MockControl.createControl(HttpServletRequest.class);

然后,即可獲得MockControl創(chuàng)建的Mock對象:

    HttpServletRequest request = (HttpServletRequest)mc.getMock();

下一步,我們需要“錄制”Mock對象的預(yù)期行為。在LoginServlet中,先后調(diào)用了request.getParameter("username")和request.getParameter("password")兩個方法,因此,需要在MockControl中設(shè)置這兩次調(diào)用后的指定返回值。我們期望返回的值為“admin”和“1234”:

    request.getParameter("username"); // 期望下面的測試將調(diào)用此方法,參數(shù)為"username"
    mc.setReturnValue("admin", 1); // 期望返回值為"admin",僅調(diào)用1次
    request.getParameter("password"); // 期望下面的測試將調(diào)用此方法,參數(shù)為" password"
    mc.setReturnValue("1234", 1); // 期望返回值為"1234",僅調(diào)用1次

緊接著,調(diào)用mc.replay(),表示Mock對象“錄制”完畢,可以開始按照我們設(shè)定的方式運行,我們對LoginServlet進行測試,并預(yù)期會產(chǎn)生一個RuntimeException:

    LoginServlet servlet = new LoginServlet();
    try {
        servlet.doPost(request, null);
        fail("Not caught exception!");
    }
    catch(RuntimeException re) {
        assertEquals("Login failed.", re.getMessage());
    }

由于本次測試的目的是檢查當(dāng)用戶名和口令驗證失敗后,LoginServlet是否會拋出RuntimeException,因此,response對象對測試沒有影響,我們不需要模擬它,僅僅傳入null即可。
最后,調(diào)用mc.verify()檢查Mock對象是否按照預(yù)期的方法調(diào)用正常運行了。
運行JUnit,測試通過!表示我們的Mock對象正確工作了!

下一步,我們來測試當(dāng)用戶名和口令匹配時,LoginServlet應(yīng)當(dāng)把請求轉(zhuǎn)發(fā)給指定的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()的語句順序,我們設(shè)定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()方法返回我們創(chuàng)建的ServletContext Mock對象,我們定義一個匿名類并覆寫getServletContext()方法:

    LoginServlet servlet = new LoginServlet() {
        public ServletContext getServletContext() {
            return contextObj;
        }
    };
    servlet.doPost(requestObj, null);

最后,檢查所有Mock對象的狀態(tài):

    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代碼如下:
/**
 * LoginServletTest.java
 * Author: Liao Xue Feng,
www.
 */
package com.crackj2ee.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();
        // 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();
    }
    public void testLoginOK() throws Exception {
        // create mock:
        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();
        // set behavior:
        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);
        // done!
        requestCtrl.replay();
        contextCtrl.replay();
        dispatcherCtrl.replay();
        // test:
        LoginServlet servlet = new LoginServlet() {
            public ServletContext getServletContext() {
                return contextObj;
            }
        };
        servlet.doPost(requestObj, null);
        // verify:
        requestCtrl.verify();
        contextCtrl.verify();
        dispatcherCtrl.verify();
    }
}

總結(jié)
雖然EasyMock可以用來模仿依賴對象,但是,它只能動態(tài)模仿接口,無法模仿具體類。這一限制正好要求我們遵循“針對接口編程”的原則:如果不針對接口,則測試難于進行。應(yīng)當(dāng)把單元測試看作是運行時代碼的最好運用,如果代碼在單元測試中難于應(yīng)用,則它在真實環(huán)境中也將難于應(yīng)用。總之,創(chuàng)建盡可能容易測試的代碼就是創(chuàng)建高質(zhì)量的代碼。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多