jMock之Expections

快速上手教程

一个消息发布与订阅系统的例子

此处,我们通过一个简单的示例来为读者示范jMock 的使用方法。这是一个简化了的消息发布与订阅系统的例子,是典型的Observer 模式,熟悉设计模式的读者对此一定不会陌生。我们用Publisher来代表消息发送方,用Subscriber 来代表消息订阅方(即接收方)。以下是Subscriber 接口的定义:

interface Subscriber {
void receive(String message);
}

一个Publisher 可以将消息(此处以String 类型的字符串对象来表达)发送给0 个或多个Subscriber 的具体实现类。而Subscriber 的具体实现类则通过Publisher 提供的接口向其注册。在本例中,我们旨在测试Publisher 的执行逻辑,而不关心具体Subscriber 的实现逻辑。为此,我们需要构造一个Mock 对象用以模拟Subscriber 的行为。然后将其注册到Publisher 里。

首先,我们必须引入jMock 的相关包,并构造一个Mockery 对象。该对象是jMock 提供Mock 能力的统一入口,后面我们将利用它来模拟Subscriber 的行为,并用它来检验Publisher 对Subscriber的模拟对象调用过程的正确性。

 import org.jmock.Expectations;
class PublisherTest extends TestCase {
Mockery context = new Mockery();
...
}
现在,我们来编写测试方法,该测试方法的测试场景是:由Publisher 向注册其中的一个Subscriber 实例发送一条消息。
public void testOneSubscriberReceivesAMessage() {
...
}
我们先利用Mockery 实例来构造一个模拟的Subscriber 对象,再构造一个Publisher 对象,并将Subscriber 注册其中,然后,我们再定义一则待发布的消息。
final Subscriber subscriber = context.mock(Subscriber.
class);
Publisher publisher = new Publisher();
publisher.add(subscriber);
final String message = "message";
紧接着,我们利用Mockery 来为模拟的Subscriber 对象定义“Expectations”——指定Publisher与Subscriber 的交互规则。此处的Expectations 是jMock 框架的一个概念。简言之,Expectations 是一组约束规则,它用于定义在测试运行期间,我们期望Mock 对象接受调用的方式。例如在本例中,我们期望Publisher 会调用Subscriber 的receive 方法一次,并且receive 方法会接收到一个String 类型的message 对象。有关Expectations 的详细说明请见后文。
 context.checking(new Expectations() {{
one (subscriber).receive(message);
}});
Expectations 是jMock 的一大特色,是它有别于其他Mock 测试工具的主要特征。jMock 提供了一整套丰富而灵活、简洁而紧凑的Expectations,其表达形式也很接近自然语言。整个Mock 对象的构造过程,即是利用一两行Expectations 的定义来完成的。这是内嵌式DSL 的一个典型应用,按jMock作者的说法,jMock 的Expectations 是Mock 测试这一特殊领域的DSL。接下来,我们开始调用Publisher 的执行逻辑,并验证调用后的结果:
publisher.publish(message);
context.assertIsSatisfied();

此处,我们再次利用了Mockery 实例,用以验证Publisher 对Subscriber 的调用是否如期执行。假如调用并非如预期的那样,则测试会失败。

以下是完整的示例代码:

import org.jmock.Mockery;
import org.jmock.Expectations;
class PublisherTest extends TestCase {
Mockery context = new Mockery();
public void testOneSubscriberReceivesAMessage() {
// set up
final Subscriber subscriber = context.
mock(Subscriber.class);
Publisher publisher = new Publisher();
publisher.add(subscriber);
final String message = "message";
// expectations
context.checking(new Expectations() {{
one (subscriber).receive(message);
}});
// execute
publisher.publish(message);
// verify
context.assertIsSatisfied();
}
}
通过上面的示例,我们可以归纳出利用jMock 进行Mock 测试的一般过程,用伪代码整理如下:
... 创建Mockery对象 ...
public void testSomeAction() {
... 一些set up的工作 ...
context.checking(new Expectations() {{
... 此处定义Expectations ...
}});
... 调用被测逻辑 ...
context.assertIsSatisfied();
... 执行其他断言 ...
}


Expectations用法简介

jMock 的Expectations 具有如下结构:

invocation-count (mock-object).method(argumentconstraints);
inSequence(sequence-name);
when(state-machine.is(state-name));
will(action);
then(state-machine.is(new-state-name));

其中,mock-object 是事先构造好的Mock 对象,如前例的Subscriber;而method 则是即将接受调用的Mock 对象的方法名称,如前例Subscriber 接口的receive 方法。除去invocation-count 和mock-object 外,后续内容都是可选的。同时,你也可以根据实际需要,为某个Expectation 追加多个inSequence、when、will 和then 子句。

代表期望的方法调用次数,jMock 提供了表达方法调用次数的多种手段,如表18-1 所示:

表18-1 jMock提供的方法调用次数的表达形式

jMock之Expections_第1张图片 

argument-constraints

代表方法调用传入参数的约束条件,可以是精确匹配的条件,如下例所示,calculator 的add 方

法只期望接受两个整数1 作为参数:

one (calculator).add(1, 1);
也可以利用with 子句定义模糊匹配条件,同样是calculator 的add 方法,在下例中则期望接受任意int 类型的参数:
allowing (calculator).add(with(any(int.class)),
with(any(int.class)));

除any 外,jMock 还提供了各种其他形式的参数约束子句,如表18-2 所示:

表18-2 jMock提供的参数约束子句

jMock之Expections_第2张图片 

will

代表方法调用返回情况的约束条件,jMock 提供的返回约束如表18-3 所示:

表18-3 jMock提供的返回约束

 


inSequence

用于定义多个方法调用的执行顺序,inSequence 子句可以定义多个,其在测试代码中出现的次序,便是方法调用的执行顺序。为了定义一个新的顺序,首先需要定义一个Sequence 对象,如下所示:

final Sequence sequence-name = context.sequence("sequencename");
而后,为了定义方法调用的执行顺序,可以在依序写好的每个Expectation 后面添加一个inSequence 子句。如下所示:
one (turtle).forward(10); inSequence(drawing);
one (turtle).turn(45); inSequence(drawing);
one (turtle).forward(10); inSequence(drawing);

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

你可能感兴趣的:(jMock之Expections)