模拟对象
从实现角度而言,模拟对象更加复杂。模拟对象可以验证待测对象与其协作对象的交互。由于具体实现方式不同,有些模拟对象可以返回硬编码的值,而有些可以提供逻辑的伪实现。模拟对象通常由框架或类库(如EasyMock、JMock)动态产生,不过也可以手工实现。
从前面的讲述中可以知道,使用JUnit4进行单元测试的时候,被测试代码需要和它所调用的代码隔离开。那么调用谁呢,用JMock获得的模拟对象就是最方便的选择。Mock方法是单元测试中常用的一种技术,它的主要作用是模拟一些在应用中不容易构造或者比较复杂的对象,例如Servlet、ResultSet、HttpServletRequest、HttpServletResponse、HttpSession等,从而把测试与测试边界以外的对象隔离开。
1、模拟对象JMock—模拟对象的意义
JMock简介
JMock是写单元测试时需要生成mock对象时很好的辅助库。
将jmock-2.5.1.jar和jmock-junit4-2.5.1.jar添加到工程的classpath。
测试用例:
//表示使用JMock运行测试用例
package com.jhaso.main;
import org.jmock.integration.junit4.JMock; import org.junit.runner.RunWith;
@RunWith(JMock.class) public class JMockTest {
}
|
2、模拟对象JMock—JMock准备
Mock对象上下文:Mockery类代表着所测试对象与之交互的测试环境.
3、模拟对象JMock—获得模拟对象
根据期望值测试(Tests with Expectations)
import junit.framework.Assert; import org.jmock.Expectations; import org.jmock.Mockery; import org.jmock.integration.junit4.JMock; import org.jmock.integration.junit4.JUnit4Mockery; import org.junit.Test; import org.junit.runner.RunWith; import com.jhaso.service.UserEbo;
/** * @author 铁臂金刀 */ @RunWith(JMock.class) public class TestUserEbo { //模拟接口 Mockery context = new JUnit4Mockery();
/** * JMock的用法 * 1.期望值 * 2.真实值 * 在调用被测试方法之前,需要把mock的dao放进去,它才能工作 * 3.用断言比较 */ @Test public void test1(){ //模拟对象: final UserDao userDao= context.mock(UserDao.class); //什么方法在什么情况下,返回什么值
context.checking(new Expectations(){ { //设置被调用的次数 oneOf(userDao).login("jhaso","jhaso"); //方法返回值 will(returnValue("ok")); } });
|
UserEbo ebo = new UserEbo(); ebo.setDao(userDao); final boolean expected = true; final boolean actual = ebo.login("jhaso", "jhaso"); Assert.assertEquals(expected, actual); } }
|
【注意】:({});两个花括号,仍然是使用匿名内部类,外面那对{}的作用相当于方法的开始与结尾;里面那对{}是子类的构造方法
4、模拟对象JMock—根据期望测试
invocation-count(mock-object).method(argument-constraints);
inSequence(sequence-name);
when(state-machine.is(state-name));
will(action);
then(state-machine.is(new-state-name));
invocation-count 调用的次数约束
mock-object mock对象
method 方法
argument-constraints 参数约束
inSequence顺序
when mockery的状态机
will(action) 方法触发的动作
then 方法触发后设置mockery的状态
5、模拟对象JMock—JMock语法
//具体相等
allowing(dao).sayHello("ok");
//具体相等
allowing(dao).sayHello(with(equal("ok")));
//字符串包含
allowing(dao).sayHello(with(newStringContains("ok")));
//==判断
allowing(dao).sayHello(with(same("ok")));
//String类型的null
allowing(dao).sayHello(with(aNull(String.class)));
//String类型,但是不是null
allowing(dao).sayHello(with(aNonNull(String.class)));
//String类型的任何东西,可以包含null
allowing(dao).sayHello(with(any(String.class)));
6、模拟对象Jmock的返回值
//模拟对象JMock—调用的次数约束
返回值:oneOf(anObject).doSomething(); will(returnValue(10));
在连续调用中返回不同值:
oneOf(anObject).doSomething(); will(returnValue(10));
oneOf(anObject).doSomething(); will(returnValue(20));
oneOf(anObject).doSomething(); will(returnValue(30));
第一次调用doSomething会返回10,第二次返回20,第三次返回30.
从模拟方法抛出异常:
allowing (bank).withdraw(with(any(Money.class)));
will(throwException(newWithdrawalLimitReachedException());
7、模拟对象JMock—方法返回值
不同参数值返回不同结果:
allowing (calculator).add(1,1); will(returnValue(3));
allowing (calculator).add(2,2); will(returnValue(5));
allowing (calculator).sqrt(-1); will(throwException(newException());
讲了使用JMock获得的模拟对象,下面再讲讲使用EasyMock获得的模拟对象。
1、EasyMock提供了根据指定接口动态构建Mock对象的方法,避免了手工编写Mock对象。EasyMock是一套通过简单的方法对于指定的接口或类生成Mock(模拟)对象的类库,它能利用对接口或类的模拟来辅助单元测试。它提供对接口的模拟,能够通过录制、回放、检查3步来完成测试过程,也可以验证方法的调用种类、次数、顺序,它可以想Jmock一样令Mock对象返回指定的值或抛出指定的异常,通过EasyMock,我们也可以方便地构造Mock对象从而使单元测试顺利进行。
将easymock.jar添加到工程的classpath中即可。如果是Maven项目可在pom.xml中添加一下代码:
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>3.4</version>
</dependency>
EasyMock 采用“记录/回放”的工作模式,基本使用步骤如下:
创建Mock对象的控制对象Control。
从控制对象中获取所需要的Mock对象。
记录测试方法中所使用到的方法和返回值。
设置Control对象到“回放”模式。
进行测试。
在测试完毕后,确认Mock对象已经执行了刚才定义的所有操作。
下面我们以HttpServlet为例,来编写一个登陆Servlet类,它从request对象中取出用户名和秘密,然后进行登陆验证。代码如下:
public class LoginServlet extends HttpServlet{ public void doPost(HttpServletResponse response,HttpServletRequest request) throws ServletException,IOException{ String userName = request.getParameter("userName"); String passWord = request.getParameter("passWord"); System.out.println("用戶名 : " + userName); System.out.println("秘密 : " + passWord); } }
|
使用EasyMock模拟创建request和response对象,并可以模拟他们的属性、方法、及返回值。这样就可以通过EasyMock来辅助完成单元测试了。
1)创建HttpServletRequest模拟对象。
2)设置HttpServletRequest对象的模拟属性和方法。
3)设置回访模式。
4)创建LoginServlet实例,并使用模拟的request执行测试。
5)调用verify()执行验证。
测试代码如下
package com.jhaso.servlet;
import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import junit.framework.TestCase; import org.easymock.EasyMock; public class LoginServletTest extends TestCase { /** * * @throws IOException * @throws ServletException */ public void testDoPost() throws IOException,ServletException{ /** * 1)创建HttpServletRequest模拟对象。 * 2)设置HttpServletRequest对象的模拟属性和方法 * 3)设置回访模式 * 4)创建LoginServlet实例,并使用模拟的request执行测试 * 5)调用verify()执行验证 */ //获取Mock對象 HttpServletRequest request = EasyMock.createMock(HttpServletRequest.class);
|
//模拟request.getParameter()的值
EasyMock.expect(request.getParameter("userName")).andReturn("admin"); EasyMock.expect(request.getParameter("passWord")).andReturn("admin"); //設置回访模式 EasyMock.replay(request); //进行测试 LoginServlet servlet = new LoginServlet(); servlet.doPost(null, request); //在调用的模拟对象前,一定要执行verify操作 EasyMock.verify(request); } }
|
其中最重要的是使用expect()来设置模拟对象request的属性,它还包含以下的模拟函数。
expectAndReturn(): 设置期望调用的函数及返回值。
expectAndThrow(): 设置期望调用的函数,同时期望该次调用抛出异常。
setReturnValue(): 设置上一次调用的返回值。
setThrowable(): 设置上次调用抛出的异常。