jmock2.5基本教程 转载
2011-04-20 18:42
148 查看
转载地址 :http://www.iteye.com/topic/770438
jmock2.5基本教程
目录
第0章 概述
第1章 jmock初体验
第2章 期望
第3章 返回值
第4章 参数匹配
第5章 指定方法调用次数
第6章 指定执行序列
第7章 状态机
第0章 概述
现在的dev不是仅仅要写code而已,UT已经变为开发中不可缺少的一环。JUnit的出现给javaer的UT编写提供了巨大的便利。但是JUnit并没有解决所有的问题。
当我们要测试一个功能点的时候,需要把不需要我们关注的东西隔离开,从而可以只关注我们需要关注的行为。
jmock通过mock对象来模拟一个对象的行为,从而隔离开我们不关心的其他对象,使得UT的编写变得更为可行,也使得TDD变得更为方便,自然而然的,也就成为敏捷开发的一个利器。
可以到http://www.jmock.org/download.html下载jmock.
添加jar到classpath。
添加的时候,注意把JUnit4的order放到最后。因为junit4它自己带了一个Hamcrest jar。
要是不注意顺序的话,有可能报
java.lang.SecurityException: class "org.hamcrest.TypeSafeMatcher"'s signer information does not match signer information of other classes in the same package。
Note:
这里的类定义用来演示如何使用jmock,所以都是定义为public的。
Java代码
public class UserManager {
public AddressService addressService;
public Address findAddress(String userName) {
return addressService.findAddress(userName);
}
public Iterator<Address> findAddresses(String userName) {
return addressService.findAddresses(userName);
}
}
我们有一个UserManager,要测试它的方法,但是,UserManager是依赖于AddressService的。这里我们准备mock掉AddressService。
第1章 jmock初体验
这个例子的作用在于像一个传统的hello world一样,给大家一个简明的介绍,可以有一个感觉,jmock可以做什么。
AddressService本身太复杂,很难构建,这个时候,jmock出场了。
Java代码
@Test
public void testFindAddress() {
// 建立一个test上下文对象。
Mockery context = new Mockery();
// 生成一个mock对象
final AddressService addressServcie = context
.mock(AddressService.class);
// 设置期望。
context.checking(new Expectations() {
{
// 当参数为"allen"的时候,addressServcie对象的findAddress方法被调用一次,并且返回西安。
oneOf(addressServcie).findAddress("allen");
will(returnValue(Para.Xian));
}
});
UserManager manager = new UserManager();
// 设置mock对象
manager.addressService = addressServcie;
// 调用方法
Address result = manager.findAddress("allen");
// 验证结果
Assert.assertEquals(Result.Xian, result);
}
那么这里做了什么事情呢?
1 首先,我们建立一个test上下文对象。
2 用这个mockery context建立了一个mock对象来mock AddressService.
3 设置了这个mock AddressService的findAddress应该被调用1次,并且参数为"allen"。
4 生成UserManager对象,设置addressService,调用findAddress。
5 验证期望被满足。
基本上,一个简单的jmock应用大致就是这样一个流程。
最显著的优点就是,我们没有AddressService的具体实现,一样可以测试对AddressService接口有依赖的其他类的行为。也就是说,我们通过moc
19974
k一个对象来隔离这个对象对要测试的代码的影响。
由于大致的流程是一样的,我们提供一个抽象类来模板化jmock的使用。
Java代码
public abstract class TestBase {
// 建立一个test上下文对象。
protected Mockery context = new Mockery();
// 生成一个mock对象
protected final AddressService addressServcie = context
.mock(AddressService.class);
/**
* 要测试的userManager.
* */
protected UserManager manager;
/**
* 设置UserManager,并且设置mock的addressService。
* */
private void setUpUserManagerWithMockAddressService() {
manager = new UserManager();
// 设置mock对象
manager.addressService = addressServcie;
}
/**
* 调用findAddress,并且验证返回值。
*
* @param userName
* userName
* @param expected
* 期望返回的地址。
* */
protected void assertFindAddress(String userName, Address expected) {
Address address = manager.findAddress(userName);
Assert.assertEquals(expected, address);
}
/**
* 调用findAddress,并且验证方法抛出异常。
* */
protected void assertFindAddressFail(String userName) {
try {
manager.findAddress(userName);
Assert.fail();
} catch (Throwable t) {
// Nothing to do.
}
}
@Test
public final void test() {
setUpExpectatioin();
setUpUserManagerWithMockAddressService();
invokeAndVerify();
}
/**
* 建立期望。
* */
protected abstract void setUpExpectatioin();
/**
* 调用方法并且验证结果。
* */
protected abstract void invokeAndVerify();
}
这样一来,我们以后的例子中只用关心setUpExpectatioin()和invokeAndVerify()方法就好了。
第2章 期望
好了,让我们来看看一个期望的框架。
Java代码
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的状态
这个稍微复杂一些,一下子不明白是正常的,后面讲到其中的细节时,可以回来在看看这个框架。
第3章 返回值
调用一个方法,可以设置它的返回值。即设置will(action)。
Java代码
@Override
protected void setUpExpectatioin() {
context.checking(new Expectations() {
{
// 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
allowing(addressServcie).findAddress("allen");
will(returnValue(Para.BeiJing));
// 当参数为null的时候,抛出IllegalArgumentException异常。
allowing(addressServcie).findAddress(null);
will(throwException(new IllegalArgumentException()));
}
});
}
@Override
protected void invokeAndVerify() {
assertFindAddress("allen", Result.BeiJing);
assertFindAddressFail(null);
}
这里演示了两种调用方法的结果,返回值和抛异常。
使用jmock可以返回常量值,也可以根据变量生成返回值。
抛异常是同样的,可以模拟在不同场景下抛的各种异常。
对于Iterator的返回值,jmock也提供了特殊支持。
Java代码
@Override
protected void setUpExpectatioin() {
// 生成地址列表
final List<Address> addresses = new ArrayList<Address>();
addresses.add(Para.Xian);
addresses.add(Para.HangZhou);
final Iterator<Address> iterator = addresses.iterator();
// 设置期望。
context.checking(new Expectations() {
{
// 当参数为"allen"的时候,addressServcie对象的findAddresses方法用returnvalue返回一个Iterator<Address>对象。
allowing(addressServcie).findAddresses("allen");
will(returnValue(iterator));
// 当参数为"dandan"的时候,addressServcie对象的findAddresses方法用returnIterator返回一个Iterator<Address>对象。
allowing(addressServcie).findAddresses("dandan");
will(returnIterator(addresses));
}
});
}
@Override
protected void invokeAndVerify() {
Iterator<Address> resultIterator = null;
// 第1次以"allen"调用方法
resultIterator = manager.findAddresses("allen");
// 断言返回的对象。
assertIterator(resultIterator);
// 第2次以"allen"调用方法,返回的与第一次一样的iterator结果对象,所以这里没有next了。
resultIterator = manager.findAddresses("allen");
Assert.assertFalse(resultIterator.hasNext());
// 第1次以"dandan"调用方法
resultIterator = manager.findAddresses("dandan");
// 断言返回的对象。
assertIterator(resultIterator);
// 第2次以"dandan"调用方法,返回的是一个全新的iterator。
resultIterator = manager.findAddresses("dandan");
// 断言返回的对象。
assertIterator(resultIterator);
}
/** 断言resultIterator中有两个期望的Address */
private void assertIterator(Iterator<Address> resultIterator) {
Address address = null;
// 断言返回的对象。
address = resultIterator.next();
Assert.assertEquals(Result.Xian, address);
address = resultIterator.next();
Assert.assertEquals(Result.HangZhou, address);
// 没有Address了。
Assert.assertFalse(resultIterator.hasNext());
}
从这个例子可以看到对于Iterator,returnValue和returnIterator的不同。
Java代码
@Override
protected void setUpExpectatioin() {
// 设置期望。
context.checking(new Expectations() {
{
// 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
allowing(addressServcie).findAddress("allen");
will(new Action() {
@Override
public Object invoke(Invocation invocation)
throws Throwable {
return Para.Xian;
}
@Override
public void describeTo(Description description) {
}
});
}
});
}
@Override
protected void invokeAndVerify() {
assertFindAddress("allen", Result.Xian);
}
其实这里要返回一个Action,该Action负责返回调用的返回值。既然知道了这个道理,我们自然可以自定义Action来返回方法调用的结果。
而returnValue,returnIterator,throwException只不过是一些Expectations提供的一些static方法用来方便的构建不同的Action。
除了刚才介绍的
ReturnValueAction 直接返回结果
ThrowAction 抛出异常
ReturnIteratorAction 返回Iterator
还有
VoidAction
ReturnEnumerationAction 返回Enumeration
DoAllAction 所有的Action都执行,但是只返回最后一个Action的结果。
ActionSequence 每次调用返回其Actions列表中的下一个Action的结果。
CustomAction 一个抽象的Action,方便自定义Action。
举个例子来说明DoAllAction和ActionSequence的使用。
Java代码
@Override
protected void setUpExpectatioin() {
// 设置期望。
context.checking(new Expectations() {
{
// doAllAction
allowing(addressServcie).findAddress("allen");
will(doAll(returnValue(Para.Xian), returnValue(Para.HangZhou)));
// ActionSequence
allowing(addressServcie).findAddress("dandan");
will(onConsecutiveCalls(returnValue(Para.Xian),
returnValue(Para.HangZhou)));
}
});
}
@Override
protected void invokeAndVerify() {
assertFindAddress("allen", Result.HangZhou);
assertFindAddress("dandan", Result.Xian);
assertFindAddress("dandan", Result.HangZhou);
}
第4章 参数匹配
即设置argument-constraints
Java代码
@Override
protected void setUpExpectatioin() {
// 设置期望。
context.checking(new Expectations() {
{
// 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
allowing(addressServcie).findAddress("allen");
will(returnValue(Para.Xian));
// 当参数为"dandan"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
allowing(addressServcie).findAddress(with(equal("dandan")));
will(returnValue(Para.HangZhou));
// 当参数包含"zhi"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
allowing(addressServcie).findAddress(
with(new BaseMatcher<String>() {
@Override
public boolean matches(Object item) {
String value = (String) item;
if (value == null)
return false;
return value.contains("zhi");
}
@Override
public void describeTo(Description description) {
}
}));
will(returnValue(Para.BeiJing));
// 当参数为其他任何值的时候,addressServcie对象的findAddress方法返回一个Adress对象。
allowing(addressServcie).findAddress(with(any(String.class)));
will(returnValue(Para.ShangHai));
}
});
}
@Override
protected void invokeAndVerify() {
// 以"allen"调用方法
assertFindAddress("allen", Result.Xian);
// 以"dandan"调用方法
assertFindAddress("dandan", Result.HangZhou);
// 以包含"zhi"的参数调用方法
assertFindAddress("abczhidef", Result.BeiJing);
// 以任意一个字符串"abcdefg"调用方法
assertFindAddress("abcdefg", Result.ShangHai);
}
测试演示了直接匹配,equal匹配,自定义匹配,任意匹配。
其实,这些都是为了给参数指定一个Matcher,来决定调用方法的时候,是否接收这个参数。
在Expectations中提供了一些便利的方法方便我们构造Matcher.
其中
equal判断用equal方法判断是否相等。
same判断是否是同一个引用。
any,anything接收任意值。
aNull接收null。
aNonNull接收非null.
jmock提供了很多有用的匹配。可以用来扩展写出更多的Matcher。
基本Matcher
IsSame 引用相等。
IsNull
IsInstanceOf
IsEqual 考虑了数组的相等(长度相等,内容equals)
IsAnything always return true.
逻辑Matcher
IsNot
AnyOf
AllOf
其他
Is 装饰器模式的Matcher,使得可读性更高。
第5章 指定方法调用次数
可以指定方法调用的次数。即对invocation-count进行指定。
exactly 精确多少次
oneOf 精确1次
atLeast 至少多少次
between 一个范围
atMost 至多多少次
allowing 任意次
ignoring 忽略
never 从不执行
可以看出,这些range都是很明了的。只有allowing和ignoring比较特殊,这两个的实际效果是一样的,但是关注点不一样。当我们允许方法可以任意次调用时,用allowing,当我们不关心一个方法的调用时,用ignoring。
第6章 指定执行序列
Java代码
@Override
protected void setUpExpectatioin() {
final Sequence sequence = context.sequence("mySeq_01");
// 设置期望。
context.checking(new Expectations() {
{
// 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
oneOf(addressServcie).findAddress("allen");
inSequence(sequence);
will(returnValue(Para.Xian));
// 当参数为"dandan"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
oneOf(addressServcie).findAddress("dandan");
inSequence(sequence);
will(returnValue(Para.HangZhou));
}
});
}
@Override
protected void invokeAndVerify() {
assertFindAddress("allen", Result.Xian);
assertFindAddress("dandan", Result.HangZhou);
}
这里指定了调用的序列。使得调用必须以指定的顺序调用。
来看一个反例
Java代码
@Override
protected void setUpExpectatioin() {
final Sequence sequence = context.sequence("mySeq_01");
// 设置期望。
context.checking(new Expectations() {
{
// 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
oneOf(addressServcie).findAddress("allen");
inSequence(sequence);
will(returnValue(Para.Xian));
// 当参数为"dandan"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
oneOf(addressServcie).findAddress("dandan");
inSequence(sequence);
will(returnValue(Para.HangZhou));
}
});
}
@Override
protected void invokeAndVerify() {
assertFindAddressFail("dandan");
}
当指定序列的第一个调用没有触发的时候,直接调用第2个,则会抛异常。
Note:指定序列的时候注意方法调用次数这个约束,如果是allowing那么在这个序列中,它是可以被忽略的。
第7章 状态机
状态机的作用在于模拟对象在什么状态下调用才用触发。
Java代码
@Override
protected void setUpExpectatioin() {
final States states = context.states("sm").startsAs("s1");
// 设置期望。
context.checking(new Expectations() {
{
// 状态为s1参数包含allen的时候返回西安
allowing(addressServcie).findAddress(
with(StringContains.containsString("allen")));
when(states.is("s1"));
will(returnValue(Para.Xian));
// 状态为s1参数包含dandan的时候返回杭州,跳转到s2。
allowing(addressServcie).findAddress(
with(StringContains.containsString("dandan")));
when(states.is("s1"));
will(returnValue(Para.HangZhou));
then(states.is("s2"));
// 状态为s2参数包含allen的时候返回上海
allowing(addressServcie).findAddress(
with(StringContains.containsString("allen")));
when(states.is("s2"));
will(returnValue(Para.ShangHai));
}
});
}
@Override
protected void invokeAndVerify() {
// s1状态
assertFindAddress("allen", Result.Xian);
assertFindAddress("allen0", Result.Xian);
// 状态跳转到 s2
assertFindAddress("dandan", Result.HangZhou);
// s2状态
assertFindAddress("allen", Result.ShangHai);
}
可以看到,如果序列一样,状态也为期望的执行设置了约束,这里就是用状态来约束哪个期望应该被执行。
可以用is或者isNot来限制状态。
状态机有一个很好的用处。
当我们建立一个test执行上下文的时候,如果建立的时候和执行的时候,我们都需要调用mock ojbect的方法,那么我们可以用状态机把这两部分隔离开。让他们在不同的状态下执行
jmock2.5基本教程
目录
第0章 概述
第1章 jmock初体验
第2章 期望
第3章 返回值
第4章 参数匹配
第5章 指定方法调用次数
第6章 指定执行序列
第7章 状态机
第0章 概述
现在的dev不是仅仅要写code而已,UT已经变为开发中不可缺少的一环。JUnit的出现给javaer的UT编写提供了巨大的便利。但是JUnit并没有解决所有的问题。
当我们要测试一个功能点的时候,需要把不需要我们关注的东西隔离开,从而可以只关注我们需要关注的行为。
jmock通过mock对象来模拟一个对象的行为,从而隔离开我们不关心的其他对象,使得UT的编写变得更为可行,也使得TDD变得更为方便,自然而然的,也就成为敏捷开发的一个利器。
可以到http://www.jmock.org/download.html下载jmock.
添加jar到classpath。
添加的时候,注意把JUnit4的order放到最后。因为junit4它自己带了一个Hamcrest jar。
要是不注意顺序的话,有可能报
java.lang.SecurityException: class "org.hamcrest.TypeSafeMatcher"'s signer information does not match signer information of other classes in the same package。
Note:
这里的类定义用来演示如何使用jmock,所以都是定义为public的。
Java代码
public class UserManager {
public AddressService addressService;
public Address findAddress(String userName) {
return addressService.findAddress(userName);
}
public Iterator<Address> findAddresses(String userName) {
return addressService.findAddresses(userName);
}
}
public class UserManager { public AddressService addressService; public Address findAddress(String userName) { return addressService.findAddress(userName); } public Iterator<Address> findAddresses(String userName) { return addressService.findAddresses(userName); } }
我们有一个UserManager,要测试它的方法,但是,UserManager是依赖于AddressService的。这里我们准备mock掉AddressService。
第1章 jmock初体验
这个例子的作用在于像一个传统的hello world一样,给大家一个简明的介绍,可以有一个感觉,jmock可以做什么。
AddressService本身太复杂,很难构建,这个时候,jmock出场了。
Java代码
@Test
public void testFindAddress() {
// 建立一个test上下文对象。
Mockery context = new Mockery();
// 生成一个mock对象
final AddressService addressServcie = context
.mock(AddressService.class);
// 设置期望。
context.checking(new Expectations() {
{
// 当参数为"allen"的时候,addressServcie对象的findAddress方法被调用一次,并且返回西安。
oneOf(addressServcie).findAddress("allen");
will(returnValue(Para.Xian));
}
});
UserManager manager = new UserManager();
// 设置mock对象
manager.addressService = addressServcie;
// 调用方法
Address result = manager.findAddress("allen");
// 验证结果
Assert.assertEquals(Result.Xian, result);
}
@Test public void testFindAddress() { // 建立一个test上下文对象。 Mockery context = new Mockery(); // 生成一个mock对象 final AddressService addressServcie = context .mock(AddressService.class); // 设置期望。 context.checking(new Expectations() { { // 当参数为"allen"的时候,addressServcie对象的findAddress方法被调用一次,并且返回西安。 oneOf(addressServcie).findAddress("allen"); will(returnValue(Para.Xian)); } }); UserManager manager = new UserManager(); // 设置mock对象 manager.addressService = addressServcie; // 调用方法 Address result = manager.findAddress("allen"); // 验证结果 Assert.assertEquals(Result.Xian, result); }
那么这里做了什么事情呢?
1 首先,我们建立一个test上下文对象。
2 用这个mockery context建立了一个mock对象来mock AddressService.
3 设置了这个mock AddressService的findAddress应该被调用1次,并且参数为"allen"。
4 生成UserManager对象,设置addressService,调用findAddress。
5 验证期望被满足。
基本上,一个简单的jmock应用大致就是这样一个流程。
最显著的优点就是,我们没有AddressService的具体实现,一样可以测试对AddressService接口有依赖的其他类的行为。也就是说,我们通过moc
19974
k一个对象来隔离这个对象对要测试的代码的影响。
由于大致的流程是一样的,我们提供一个抽象类来模板化jmock的使用。
Java代码
public abstract class TestBase {
// 建立一个test上下文对象。
protected Mockery context = new Mockery();
// 生成一个mock对象
protected final AddressService addressServcie = context
.mock(AddressService.class);
/**
* 要测试的userManager.
* */
protected UserManager manager;
/**
* 设置UserManager,并且设置mock的addressService。
* */
private void setUpUserManagerWithMockAddressService() {
manager = new UserManager();
// 设置mock对象
manager.addressService = addressServcie;
}
/**
* 调用findAddress,并且验证返回值。
*
* @param userName
* userName
* @param expected
* 期望返回的地址。
* */
protected void assertFindAddress(String userName, Address expected) {
Address address = manager.findAddress(userName);
Assert.assertEquals(expected, address);
}
/**
* 调用findAddress,并且验证方法抛出异常。
* */
protected void assertFindAddressFail(String userName) {
try {
manager.findAddress(userName);
Assert.fail();
} catch (Throwable t) {
// Nothing to do.
}
}
@Test
public final void test() {
setUpExpectatioin();
setUpUserManagerWithMockAddressService();
invokeAndVerify();
}
/**
* 建立期望。
* */
protected abstract void setUpExpectatioin();
/**
* 调用方法并且验证结果。
* */
protected abstract void invokeAndVerify();
}
public abstract class TestBase { // 建立一个test上下文对象。 protected Mockery context = new Mockery(); // 生成一个mock对象 protected final AddressService addressServcie = context .mock(AddressService.class); /** * 要测试的userManager. * */ protected UserManager manager; /** * 设置UserManager,并且设置mock的addressService。 * */ private void setUpUserManagerWithMockAddressService() { manager = new UserManager(); // 设置mock对象 manager.addressService = addressServcie; } /** * 调用findAddress,并且验证返回值。 * * @param userName * userName * @param expected * 期望返回的地址。 * */ protected void assertFindAddress(String userName, Address expected) { Address address = manager.findAddress(userName); Assert.assertEquals(expected, address); } /** * 调用findAddress,并且验证方法抛出异常。 * */ protected void assertFindAddressFail(String userName) { try { manager.findAddress(userName); Assert.fail(); } catch (Throwable t) { // Nothing to do. } } @Test public final void test() { setUpExpectatioin(); setUpUserManagerWithMockAddressService(); invokeAndVerify(); } /** * 建立期望。 * */ protected abstract void setUpExpectatioin(); /** * 调用方法并且验证结果。 * */ protected abstract void invokeAndVerify(); }
这样一来,我们以后的例子中只用关心setUpExpectatioin()和invokeAndVerify()方法就好了。
第2章 期望
好了,让我们来看看一个期望的框架。
Java代码
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).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的状态
这个稍微复杂一些,一下子不明白是正常的,后面讲到其中的细节时,可以回来在看看这个框架。
第3章 返回值
调用一个方法,可以设置它的返回值。即设置will(action)。
Java代码
@Override
protected void setUpExpectatioin() {
context.checking(new Expectations() {
{
// 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
allowing(addressServcie).findAddress("allen");
will(returnValue(Para.BeiJing));
// 当参数为null的时候,抛出IllegalArgumentException异常。
allowing(addressServcie).findAddress(null);
will(throwException(new IllegalArgumentException()));
}
});
}
@Override
protected void invokeAndVerify() {
assertFindAddress("allen", Result.BeiJing);
assertFindAddressFail(null);
}
@Override protected void setUpExpectatioin() { context.checking(new Expectations() { { // 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。 allowing(addressServcie).findAddress("allen"); will(returnValue(Para.BeiJing)); // 当参数为null的时候,抛出IllegalArgumentException异常。 allowing(addressServcie).findAddress(null); will(throwException(new IllegalArgumentException())); } }); } @Override protected void invokeAndVerify() { assertFindAddress("allen", Result.BeiJing); assertFindAddressFail(null); }
这里演示了两种调用方法的结果,返回值和抛异常。
使用jmock可以返回常量值,也可以根据变量生成返回值。
抛异常是同样的,可以模拟在不同场景下抛的各种异常。
对于Iterator的返回值,jmock也提供了特殊支持。
Java代码
@Override
protected void setUpExpectatioin() {
// 生成地址列表
final List<Address> addresses = new ArrayList<Address>();
addresses.add(Para.Xian);
addresses.add(Para.HangZhou);
final Iterator<Address> iterator = addresses.iterator();
// 设置期望。
context.checking(new Expectations() {
{
// 当参数为"allen"的时候,addressServcie对象的findAddresses方法用returnvalue返回一个Iterator<Address>对象。
allowing(addressServcie).findAddresses("allen");
will(returnValue(iterator));
// 当参数为"dandan"的时候,addressServcie对象的findAddresses方法用returnIterator返回一个Iterator<Address>对象。
allowing(addressServcie).findAddresses("dandan");
will(returnIterator(addresses));
}
});
}
@Override
protected void invokeAndVerify() {
Iterator<Address> resultIterator = null;
// 第1次以"allen"调用方法
resultIterator = manager.findAddresses("allen");
// 断言返回的对象。
assertIterator(resultIterator);
// 第2次以"allen"调用方法,返回的与第一次一样的iterator结果对象,所以这里没有next了。
resultIterator = manager.findAddresses("allen");
Assert.assertFalse(resultIterator.hasNext());
// 第1次以"dandan"调用方法
resultIterator = manager.findAddresses("dandan");
// 断言返回的对象。
assertIterator(resultIterator);
// 第2次以"dandan"调用方法,返回的是一个全新的iterator。
resultIterator = manager.findAddresses("dandan");
// 断言返回的对象。
assertIterator(resultIterator);
}
/** 断言resultIterator中有两个期望的Address */
private void assertIterator(Iterator<Address> resultIterator) {
Address address = null;
// 断言返回的对象。
address = resultIterator.next();
Assert.assertEquals(Result.Xian, address);
address = resultIterator.next();
Assert.assertEquals(Result.HangZhou, address);
// 没有Address了。
Assert.assertFalse(resultIterator.hasNext());
}
@Override protected void setUpExpectatioin() { // 生成地址列表 final List<Address> addresses = new ArrayList<Address>(); addresses.add(Para.Xian); addresses.add(Para.HangZhou); final Iterator<Address> iterator = addresses.iterator(); // 设置期望。 context.checking(new Expectations() { { // 当参数为"allen"的时候,addressServcie对象的findAddresses方法用returnvalue返回一个Iterator<Address>对象。 allowing(addressServcie).findAddresses("allen"); will(returnValue(iterator)); // 当参数为"dandan"的时候,addressServcie对象的findAddresses方法用returnIterator返回一个Iterator<Address>对象。 allowing(addressServcie).findAddresses("dandan"); will(returnIterator(addresses)); } }); } @Override protected void invokeAndVerify() { Iterator<Address> resultIterator = null; // 第1次以"allen"调用方法 resultIterator = manager.findAddresses("allen"); // 断言返回的对象。 assertIterator(resultIterator); // 第2次以"allen"调用方法,返回的与第一次一样的iterator结果对象,所以这里没有next了。 resultIterator = manager.findAddresses("allen"); Assert.assertFalse(resultIterator.hasNext()); // 第1次以"dandan"调用方法 resultIterator = manager.findAddresses("dandan"); // 断言返回的对象。 assertIterator(resultIterator); // 第2次以"dandan"调用方法,返回的是一个全新的iterator。 resultIterator = manager.findAddresses("dandan"); // 断言返回的对象。 assertIterator(resultIterator); } /** 断言resultIterator中有两个期望的Address */ private void assertIterator(Iterator<Address> resultIterator) { Address address = null; // 断言返回的对象。 address = resultIterator.next(); Assert.assertEquals(Result.Xian, address); address = resultIterator.next(); Assert.assertEquals(Result.HangZhou, address); // 没有Address了。 Assert.assertFalse(resultIterator.hasNext()); }
从这个例子可以看到对于Iterator,returnValue和returnIterator的不同。
Java代码
@Override
protected void setUpExpectatioin() {
// 设置期望。
context.checking(new Expectations() {
{
// 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
allowing(addressServcie).findAddress("allen");
will(new Action() {
@Override
public Object invoke(Invocation invocation)
throws Throwable {
return Para.Xian;
}
@Override
public void describeTo(Description description) {
}
});
}
});
}
@Override
protected void invokeAndVerify() {
assertFindAddress("allen", Result.Xian);
}
@Override protected void setUpExpectatioin() { // 设置期望。 context.checking(new Expectations() { { // 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。 allowing(addressServcie).findAddress("allen"); will(new Action() { @Override public Object invoke(Invocation invocation) throws Throwable { return Para.Xian; } @Override public void describeTo(Description description) { } }); } }); } @Override protected void invokeAndVerify() { assertFindAddress("allen", Result.Xian); }
其实这里要返回一个Action,该Action负责返回调用的返回值。既然知道了这个道理,我们自然可以自定义Action来返回方法调用的结果。
而returnValue,returnIterator,throwException只不过是一些Expectations提供的一些static方法用来方便的构建不同的Action。
除了刚才介绍的
ReturnValueAction 直接返回结果
ThrowAction 抛出异常
ReturnIteratorAction 返回Iterator
还有
VoidAction
ReturnEnumerationAction 返回Enumeration
DoAllAction 所有的Action都执行,但是只返回最后一个Action的结果。
ActionSequence 每次调用返回其Actions列表中的下一个Action的结果。
CustomAction 一个抽象的Action,方便自定义Action。
举个例子来说明DoAllAction和ActionSequence的使用。
Java代码
@Override
protected void setUpExpectatioin() {
// 设置期望。
context.checking(new Expectations() {
{
// doAllAction
allowing(addressServcie).findAddress("allen");
will(doAll(returnValue(Para.Xian), returnValue(Para.HangZhou)));
// ActionSequence
allowing(addressServcie).findAddress("dandan");
will(onConsecutiveCalls(returnValue(Para.Xian),
returnValue(Para.HangZhou)));
}
});
}
@Override
protected void invokeAndVerify() {
assertFindAddress("allen", Result.HangZhou);
assertFindAddress("dandan", Result.Xian);
assertFindAddress("dandan", Result.HangZhou);
}
@Override protected void setUpExpectatioin() { // 设置期望。 context.checking(new Expectations() { { // doAllAction allowing(addressServcie).findAddress("allen"); will(doAll(returnValue(Para.Xian), returnValue(Para.HangZhou))); // ActionSequence allowing(addressServcie).findAddress("dandan"); will(onConsecutiveCalls(returnValue(Para.Xian), returnValue(Para.HangZhou))); } }); } @Override protected void invokeAndVerify() { assertFindAddress("allen", Result.HangZhou); assertFindAddress("dandan", Result.Xian); assertFindAddress("dandan", Result.HangZhou); }
第4章 参数匹配
即设置argument-constraints
Java代码
@Override
protected void setUpExpectatioin() {
// 设置期望。
context.checking(new Expectations() {
{
// 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
allowing(addressServcie).findAddress("allen");
will(returnValue(Para.Xian));
// 当参数为"dandan"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
allowing(addressServcie).findAddress(with(equal("dandan")));
will(returnValue(Para.HangZhou));
// 当参数包含"zhi"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
allowing(addressServcie).findAddress(
with(new BaseMatcher<String>() {
@Override
public boolean matches(Object item) {
String value = (String) item;
if (value == null)
return false;
return value.contains("zhi");
}
@Override
public void describeTo(Description description) {
}
}));
will(returnValue(Para.BeiJing));
// 当参数为其他任何值的时候,addressServcie对象的findAddress方法返回一个Adress对象。
allowing(addressServcie).findAddress(with(any(String.class)));
will(returnValue(Para.ShangHai));
}
});
}
@Override
protected void invokeAndVerify() {
// 以"allen"调用方法
assertFindAddress("allen", Result.Xian);
// 以"dandan"调用方法
assertFindAddress("dandan", Result.HangZhou);
// 以包含"zhi"的参数调用方法
assertFindAddress("abczhidef", Result.BeiJing);
// 以任意一个字符串"abcdefg"调用方法
assertFindAddress("abcdefg", Result.ShangHai);
}
@Override protected void setUpExpectatioin() { // 设置期望。 context.checking(new Expectations() { { // 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。 allowing(addressServcie).findAddress("allen"); will(returnValue(Para.Xian)); // 当参数为"dandan"的时候,addressServcie对象的findAddress方法返回一个Adress对象。 allowing(addressServcie).findAddress(with(equal("dandan"))); will(returnValue(Para.HangZhou)); // 当参数包含"zhi"的时候,addressServcie对象的findAddress方法返回一个Adress对象。 allowing(addressServcie).findAddress( with(new BaseMatcher<String>() { @Override public boolean matches(Object item) { String value = (String) item; if (value == null) return false; return value.contains("zhi"); } @Override public void describeTo(Description description) { } })); will(returnValue(Para.BeiJing)); // 当参数为其他任何值的时候,addressServcie对象的findAddress方法返回一个Adress对象。 allowing(addressServcie).findAddress(with(any(String.class))); will(returnValue(Para.ShangHai)); } }); } @Override protected void invokeAndVerify() { // 以"allen"调用方法 assertFindAddress("allen", Result.Xian); // 以"dandan"调用方法 assertFindAddress("dandan", Result.HangZhou); // 以包含"zhi"的参数调用方法 assertFindAddress("abczhidef", Result.BeiJing); // 以任意一个字符串"abcdefg"调用方法 assertFindAddress("abcdefg", Result.ShangHai); }
测试演示了直接匹配,equal匹配,自定义匹配,任意匹配。
其实,这些都是为了给参数指定一个Matcher,来决定调用方法的时候,是否接收这个参数。
在Expectations中提供了一些便利的方法方便我们构造Matcher.
其中
equal判断用equal方法判断是否相等。
same判断是否是同一个引用。
any,anything接收任意值。
aNull接收null。
aNonNull接收非null.
jmock提供了很多有用的匹配。可以用来扩展写出更多的Matcher。
基本Matcher
IsSame 引用相等。
IsNull
IsInstanceOf
IsEqual 考虑了数组的相等(长度相等,内容equals)
IsAnything always return true.
逻辑Matcher
IsNot
AnyOf
AllOf
其他
Is 装饰器模式的Matcher,使得可读性更高。
第5章 指定方法调用次数
可以指定方法调用的次数。即对invocation-count进行指定。
exactly 精确多少次
oneOf 精确1次
atLeast 至少多少次
between 一个范围
atMost 至多多少次
allowing 任意次
ignoring 忽略
never 从不执行
可以看出,这些range都是很明了的。只有allowing和ignoring比较特殊,这两个的实际效果是一样的,但是关注点不一样。当我们允许方法可以任意次调用时,用allowing,当我们不关心一个方法的调用时,用ignoring。
第6章 指定执行序列
Java代码
@Override
protected void setUpExpectatioin() {
final Sequence sequence = context.sequence("mySeq_01");
// 设置期望。
context.checking(new Expectations() {
{
// 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
oneOf(addressServcie).findAddress("allen");
inSequence(sequence);
will(returnValue(Para.Xian));
// 当参数为"dandan"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
oneOf(addressServcie).findAddress("dandan");
inSequence(sequence);
will(returnValue(Para.HangZhou));
}
});
}
@Override
protected void invokeAndVerify() {
assertFindAddress("allen", Result.Xian);
assertFindAddress("dandan", Result.HangZhou);
}
@Override protected void setUpExpectatioin() { final Sequence sequence = context.sequence("mySeq_01"); // 设置期望。 context.checking(new Expectations() { { // 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。 oneOf(addressServcie).findAddress("allen"); inSequence(sequence); will(returnValue(Para.Xian)); // 当参数为"dandan"的时候,addressServcie对象的findAddress方法返回一个Adress对象。 oneOf(addressServcie).findAddress("dandan"); inSequence(sequence); will(returnValue(Para.HangZhou)); } }); } @Override protected void invokeAndVerify() { assertFindAddress("allen", Result.Xian); assertFindAddress("dandan", Result.HangZhou); }
这里指定了调用的序列。使得调用必须以指定的顺序调用。
来看一个反例
Java代码
@Override
protected void setUpExpectatioin() {
final Sequence sequence = context.sequence("mySeq_01");
// 设置期望。
context.checking(new Expectations() {
{
// 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
oneOf(addressServcie).findAddress("allen");
inSequence(sequence);
will(returnValue(Para.Xian));
// 当参数为"dandan"的时候,addressServcie对象的findAddress方法返回一个Adress对象。
oneOf(addressServcie).findAddress("dandan");
inSequence(sequence);
will(returnValue(Para.HangZhou));
}
});
}
@Override
protected void invokeAndVerify() {
assertFindAddressFail("dandan");
}
@Override protected void setUpExpectatioin() { final Sequence sequence = context.sequence("mySeq_01"); // 设置期望。 context.checking(new Expectations() { { // 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。 oneOf(addressServcie).findAddress("allen"); inSequence(sequence); will(returnValue(Para.Xian)); // 当参数为"dandan"的时候,addressServcie对象的findAddress方法返回一个Adress对象。 oneOf(addressServcie).findAddress("dandan"); inSequence(sequence); will(returnValue(Para.HangZhou)); } }); } @Override protected void invokeAndVerify() { assertFindAddressFail("dandan"); }
当指定序列的第一个调用没有触发的时候,直接调用第2个,则会抛异常。
Note:指定序列的时候注意方法调用次数这个约束,如果是allowing那么在这个序列中,它是可以被忽略的。
第7章 状态机
状态机的作用在于模拟对象在什么状态下调用才用触发。
Java代码
@Override
protected void setUpExpectatioin() {
final States states = context.states("sm").startsAs("s1");
// 设置期望。
context.checking(new Expectations() {
{
// 状态为s1参数包含allen的时候返回西安
allowing(addressServcie).findAddress(
with(StringContains.containsString("allen")));
when(states.is("s1"));
will(returnValue(Para.Xian));
// 状态为s1参数包含dandan的时候返回杭州,跳转到s2。
allowing(addressServcie).findAddress(
with(StringContains.containsString("dandan")));
when(states.is("s1"));
will(returnValue(Para.HangZhou));
then(states.is("s2"));
// 状态为s2参数包含allen的时候返回上海
allowing(addressServcie).findAddress(
with(StringContains.containsString("allen")));
when(states.is("s2"));
will(returnValue(Para.ShangHai));
}
});
}
@Override
protected void invokeAndVerify() {
// s1状态
assertFindAddress("allen", Result.Xian);
assertFindAddress("allen0", Result.Xian);
// 状态跳转到 s2
assertFindAddress("dandan", Result.HangZhou);
// s2状态
assertFindAddress("allen", Result.ShangHai);
}
@Override protected void setUpExpectatioin() { final States states = context.states("sm").startsAs("s1"); // 设置期望。 context.checking(new Expectations() { { // 状态为s1参数包含allen的时候返回西安 allowing(addressServcie).findAddress( with(StringContains.containsString("allen"))); when(states.is("s1")); will(returnValue(Para.Xian)); // 状态为s1参数包含dandan的时候返回杭州,跳转到s2。 allowing(addressServcie).findAddress( with(StringContains.containsString("dandan"))); when(states.is("s1")); will(returnValue(Para.HangZhou)); then(states.is("s2")); // 状态为s2参数包含allen的时候返回上海 allowing(addressServcie).findAddress( with(StringContains.containsString("allen"))); when(states.is("s2")); will(returnValue(Para.ShangHai)); } }); } @Override protected void invokeAndVerify() { // s1状态 assertFindAddress("allen", Result.Xian); assertFindAddress("allen0", Result.Xian); // 状态跳转到 s2 assertFindAddress("dandan", Result.HangZhou); // s2状态 assertFindAddress("allen", Result.ShangHai); }
可以看到,如果序列一样,状态也为期望的执行设置了约束,这里就是用状态来约束哪个期望应该被执行。
可以用is或者isNot来限制状态。
状态机有一个很好的用处。
当我们建立一个test执行上下文的时候,如果建立的时候和执行的时候,我们都需要调用mock ojbect的方法,那么我们可以用状态机把这两部分隔离开。让他们在不同的状态下执行
相关文章推荐
- jmock2.5基本教程
- jmock2.5基本教程
- jmock2.5基本教程
- jmock2.5基本教程(转)
- jmock2.5基本教程 --转载
- zedGraph基本教程篇--第一节:InitialSampleDemo.cs介绍 转载
- 08_传智播客Spring2.5视频教程_编码剖析Spring装配基本属性的原理
- 转载:Qt5基本教程
- 转载-smarty教程(基本语法)
- HttpClient 4.3教程 第一章 基本概念(转载)
- (转载)Linux上iptables防火墙的基本应用教程
- 联想Y450+Mountain Lion 10.8.4+win8双系统 基本完美安装,运行教程(转载)
- p5.js入门教程和基本形状绘制
- NPOI 2.0 教程(三):EXCEL 基本格式设置之ICellStyle
- GDB工具使用教程_基本调试指令
- kafka教程-基本概念
- Python程序中用csv模块来操作csv文件的基本使用教程
- JSP初学者教程学习JSP的基本指令
- SASS基础教程——SASS基本语法与特性
- shell 教程四:基本运算符(算数,关系,布尔,字符串,文件检测)