技术开发 频道

EasyMock使单元测试更加容易

【IT168 技术文章】

    单元测试是XP极力推荐的测试驱动开发模式,是保证软件质量的重要方法。尽管如此,对许多类的单元测试仍然是极其困难的,例如,对数据库操作的类进行测试,如果不准备好数据库环境以及相关测试数据,是很难进行单元测试的;再例如,对需要运行在容器内的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类:

/**

* LoginServlet.java

* Author: Liao Xue Feng, http://www.crackj2ee.com/

*/

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实现简单的用户验证的功能,若用户名和口令匹配“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对象正确工作了!

0
相关文章