Mock测试之JUnit+JMock双剑合璧

 

简单说下Jmock,Jmock - HelloWorld

 

 

由于软件项目包测试课程老师要求我去学习一下JMock并总结分享给其他同学,没办法,既然老师这么看得起我,怎么说也不能让她失望是吧!

 

1.什么是**Mock**测试

—mock测试:就是在测试过程中,对于某些不容易构造或者 不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。

—比喻举例:你要测试一个人的吃饭功能是否正常,你还得去帮他建立一个呼吸系统,循环系统,新陈代谢系统甚至建立一张脸,一个身体等等。待会要测试他的说话功能,又得重新为这个人建立这一些系统,麻烦!

—而Mock(虚拟对象)则是为了解决这一系列与测试内容不相关的事情。

—现实中很多运行中的对象(比如股票行情,天气预报)都很难预测,难以被创建,所以Mock的用武之地就在这里!

2.邻于**Mock**测试

—我们主要是讲用Jmock工具来实现Mock测试,Jmock官网下载地址:链接

官网wiki目录:链接

—Mock 是基于java反射机制的,关于java反射机制:百度一下或者看我的博客文章

测试先行:传统上的测试总是在代码编写完进行的,多数人也会觉得这很正常,但是新的思想是在需求分析时测试工作就已经进入了,甚至比代码人员参与项目更早。会提前于代码阶段把测试计划、用例等工作完成,可见测试介入的越早,项目就越有保证。

3.配置**JMock**

java项目引用下面四个jar 包:

· jmock-*.jar

· hamcrest-core-*.jar

· hamcrest-library-*.jar

· jmock-junit4-*.jar

如图所示:也可以把jmock添加到classpath

JMock配置1

4.例子**(JmockHelloWord)**

—类:UserDAO,UserService。

—我们的目的是测试UserService,然而UserService中调用了UserDAO,所以我们需要虚拟出一个UserDAO对象来给我们的测试用例调用。

—UserDAO负责与数据库打交道,通过数据库保存、获取User的信息。

—public interface UserDAO {   
public void saveUser(User user);
public User getUser(Long id);
—}

—UserService存有UserDAO的引用,通过其对外提供应用级的服务。

 public class UserService {
private UserDAO dao;

public UserService(UserDAO dao){ 
    this.dao = dao; 
} 

public String getUser(int num){ 
    return "得到的用户是:"+dao.getUser(num);
} 

}
单元测试类 (Junit4+jmock)

利用jmock虚拟出一个UserDAO接口的对象,给UserService类调用!

import junit.framework.TestCase;
import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.junit.Before;
import org.junit.Test;

public class UserServiceTest extends TestCase {
//Mockery:Mock对象上下文: 代表着所测试对象与之交互的测试环境
private Mockery context = new JUnit4Mockery();
private UserDAO dao = null;
private UserService service = null;

//setUp会在测试用例执行前调用
@Before
public void setUp() throws Exception {
    super.setUp();
    //利用Mockery上下文对象来模拟出一个虚拟UserDAO对象
    dao = context.mock(UserDAO.class);
    //把模拟出来的虚拟对象传入UserService的构造方法,实例化出一个Service对象
    service = new UserService(dao);
    //对虚拟对象的交互环境进行配置
    context.checking(new Expectations() {
        {
            //将虚拟对象dao的getUser方法设定一个返回值
            exactly(1).of(dao).getUser(10);
            will(returnValue("用户10"));
        }
    });
}

@Test
public void test1() {
    assertEquals("得到的用户是:用户10", service.getUser(10));
}

@Test
public void test2() {
    assertEquals("得到的用户期望是错的", service.getUser(10));
}

@Test
public void test3() {
    assertEquals(service.getUser(10), service.getUser(10));
    //按道理应该是一样的,但结果报错的
    //原因就是上面模拟对象的次数约束是 exactly(1),而下面调用了2次
}

}

测试结果:

mock3

test2 这个测试用例是我故意写错的。

有上述代码可以看出,利用mock,可以再不对UserDAO进行实现的情况下进行UserService类的测试!

可以实现目前大公司的思想:先进行测试代码的开发,再进行实现代码的编写。即:先准备测试,再进行开发。

 

上面的代码中有几个需要注意地方:
1. 测试类继承了 TestCase(junit4)

2. 创建了 Mockery 的对象 context

3 利用 context 对象来 mock (模拟) 接口的实例及对应的方法。代码中高亮部分

在这个简单的例子中我们 mock 了 UserDAO及其方法getUser(),这样程序在运行的时候就不会去执行真正 UserDAO实现类的代码,

而转到 line12 的地方。给定数值 10,直接返回 “ 用户10”。

 

所以我们mock对象的重点就是在对模拟对象的期望设定上!

其中包含5种设定:

invocation-count(mock-object).method(argument-constraints); **调用的次数约束(次数).对应的方法(参数约束);**

inSequence(sequence-name);

when(state-machine.is(state-name));mockery 的状态机

will(action);方法触发的动作

then(state-machine.is(new-state-name)); 方法触发后设置 mockery 的状态

调用次数约束 invocation-count 主要包括以下几种:

mock4

 

参数次数约束with

Jmock参数约束with

方法调用返回情况的约束条件

方法调用返回情况的约束条件will

when和then操作

用于定义方法仅当某些条件为true 的时候才调用执行,从而进一步对Mock 对象的调用情况进行约束。在jMock 中,这种约束是通过状态机的形式来达成的。首先,我们需要定义一个状态机实例,其中的初始状态(initial-state)是可选的:
final States state-machine-name =
context.states(“state-machine-name”).startsAs(“initialstate”);

 

我们可以利用when 子句来定义当处于某状态时方法被调用执行,利用then 来定义当某方法被调用执行后,状态的迁移情况。举例如下:
final States pen = context.states(“pen”).startsAs(“up”);
one (turtle).penDown(); then(pen.is(“down”));
one (turtle).forward(10); when(pen.is(“down”));
one (turtle).turn(90); when(pen.is(“down”));
one (turtle).forward(10); when(pen.is(“down”));
one (turtle).penUp(); then(pen.is(“up”));

 

具体例子:

//具体相等
allowing(dao).sayHello(“ok”);
//具体相等
allowing(dao).sayHello(with(equal(“ok”)));
//字符串包含
allowing(dao).sayHello(with(new StringContains(“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)));
返回值: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(new WithdrawalLimitReachedException());
不同参数值返回不同结果:
allowing (calculator).add(1,1); will(returnValue(3));
allowing (calculator).add(2,2); will(returnValue(5));
allowing (calculator).sqrt(-1); will(throwException(new Exception());

 

如上述代码中的:

exactly(1).of(dao).getUser(10);
will(returnValue(“用户10”));

意思是:dao这个模拟对象,的getUser方法,如果传进来的是 10,那么将会返回 “用户10”,并且能且只能且必须调用一次!

 



 

更多关于 JMock 的知识请参阅 http://www.jmock.org/cookbook.html.

 

未完待续…后面会总结一下覆盖分析。