mock技巧

replay之前,对mock对象做的操作称为training
这样做是为了不用实际写一个被依赖的对象,而是通过告诉easymock,某一方法接到什么参数时,返回什么值。
以这样的形式来建立被依赖的对象

replay调用之后,mock对象的每次被调用就都会被记录下来
到了verify的时候,检查顺序是否一致,参数是否一致
不一致就报unexpected


有点头绪了,麻烦用以下例子给我讲解一下:
    public final void testProcessOnDemandMessage()
    {
        EasyMock.expect(yiHuanHuDongDao.getUserByPhoneNumber("1234")).andReturn(
                user);

        YiHuanHuDongQuestion question = new YiHuanHuDongQuestion();
        question.setQuestion("我有病");

        yiHuanHuDongDao.saveQuestion(question);

        mocksControl.replay();

        SubmitMessage[] messages = product.processOnDemandMessage("1234", "",
                "K我有病", "ABC123");

        assertEquals(1, messages.length);
        SubmitMessage actual = messages[0];
        assertEquals("1234", actual.getDestUserPhoneNumber());
        assertNull(actual.getFeeUserPhoneNumber());
        assertTrue(actual.getMessageContent().length() > 0);
        assertNotNull(actual.getProductCategory());
        assertEquals("ABC123", actual.getLinkId());

        mocksControl.verify();
    }
 


我今天看了一些MOCK的资料,讲得比较简单。
 

EasyMock.expect(yiHuanHuDongDao.getUserByPhoneNumber("1234")).andReturn(user);

设定了yiHuanHuDongDao这个mock对象会被调用一次,方法和参数是getUserByPhoneNumber("1234"),返回是user。user在setUp里设定了。


YiHuanHuDongQuestion question = new YiHuanHuDongQuestion();question.setQuestion("我有病");yiHuanHuDongDao.saveQuestion(question);
设定yiHuanHuDongDao接下来会被调用saveQuestion方法,参数是question

 

测试时,当yiHuanHuDongDao.saveQuestion被调用时,easymock会检查参数是否跟设定时的question相等

所谓相等,就是question.equal()方法返回true


 public final void testSubscribeNormal()
 {
  VoteUser user1=null;

  EasyMock.expect(voteDao.getVoteTaskByID(8)).andReturn(task);
    
  EasyMock.expect(voteDao.getUserByPhoneNumber("13929222346")).andReturn(user);
  

  voteDao.registeUser(user);
  mocksControl.replay();
  

    
  SubmitMessage[] messages = product.subscribe("999", "13929222346", "814");

  assertEquals(1, messages.length);
  
  SubmitMessage actual = messages[0];
  assertEquals("13929222346", actual.getDestUserPhoneNumber());
  assertEquals("999", actual.getFeeUserPhoneNumber());
  assertTrue(actual.getMessageContent().length() > 0);
  assertNotNull(actual.getProductCategory());
  
  mocksControl.verify();
  
  
 }
这段代码为什么会提示
junit.framework.ComparisonFailure: expected:<999> but was:<null>
 at junit.framework.Assert.assertEquals(Assert.java:81)
 at junit.framework.Assert.assertEquals(Assert.java:87)
 at com.yxh.service.product.impl.VoteProductTest.testSubscribeNormal(VoteProductTest.java:90)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
 at java.lang.reflect.Method.invoke(Unknown Source)
 at junit.framework.TestCase.runTest(TestCase.java:154)
 at junit.framework.TestCase.runBare(TestCase.java:127)
 at junit.framework.TestResult$1.protect(TestResult.java:106)
 at junit.framework.TestResult.runProtected(TestResult.java:124)
 at junit.framework.TestResult.run(TestResult.java:109)
 at junit.framework.TestCase.run(TestCase.java:118)
 at junit.framework.TestSuite.runTest(TestSuite.java:208)
 at junit.framework.TestSuite.run(TestSuite.java:203)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:478)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:344)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)

 
 


actual.getFeeUserPhoneNumber()的返回值是null,与你期望的999不符合

这时候要查看实现代码里有没有正确设定feeUserPhoneNumber


里面有以下:
        if(!destUserPhoneNumber.equals(feeUserPhoneNumber))
        {
            message.setFeeUserPhoneNumber(feeUserPhoneNumber);
        }

代码啊。
 


找不到那里错了。
 


如果把  assertEquals("999", actual.getFeeUserPhoneNumber());去掉会提示不同的东西。
 


通过在实现代码里临时插入System.out.println来查找错误原因

或者设定断点。用Debug模式运行测试用例。

你现在遇到的已经不是mock的问题了,而是程序实现上有bug了。
所以调试方法跟通常的做法一样。


 public VoteTask getVoteTaskByID(long id) {


       
        List ret = getHibernateTemplate().find(
                "from VoteTask task where task.id = ? and task.status=1",
                id);
        return ret.isEmpty() ? null : (VoteTask) ret.get(0);
 }
这个有问题吗?怎么返回NULL?
 

replay之前,对mock对象做的操作称为training
这样做是为了不用实际写一个被依赖的对象,而是通过告诉easymock,某一方法接到什么参数时,返回什么值。
以这样的形式来建立被依赖的对象

replay调用之后,mock对象的每次被调用就都会被记录下来
到了verify的时候,检查顺
有点头绪了,麻烦用以下例子给我讲解一下:
    public final void testProcessOnDemandMessage()
    {
        EasyMock.expect(yiHuanHuDongDao.getUserByPhoneNumber("1234")).andReturn(
                user);

        YiHuanHuDongQuestion question = new YiH
测试时,当yiHuanHuDongDao.saveQuestion被调用时,easymock会检查参数是否跟设定时的question相等

所谓相等,就是question.equal()方法返回true


mocksControl = EasyMock.createStrictControl();
voteDao = mocksControl.createMock(VoteDao.class);

这两句,生成了一个模拟的VoteDao对象

 

使用EasyMock使单元测试更加容易
作者:eric 文章来源:SUN中国技术社区 点击数:554 更新时间:2006-3-1
 
 
 
单元测试是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, 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对象正确工作了!


下一步,我们来测试当用户名和口令匹配时,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代码如下:

/**

* LoginServletTest.java

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

*/

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();

}


}


总结:

虽然EasyMock可以用来模仿依赖对象,但是,它只能动态模仿接口,无法模仿具体类。这一限制正好要求我们遵循“针对接口编程”的原则:如果不针对接口,则测试难于进行。应当把单元测试看作是运行时代码的最好运用,如果代码在单元测试中难于应用,则它在真实环境中也将难于应用。总之,创建尽可能容易测试的代码就是创建高质量的代码。
 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值