jmock2.5基本教程

原URL:http://www.cnblogs.com/zfc2201/archive/2011/12/30/2307970.html

目录
第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代码 
01.public class UserManager {  
02. 
03.    public AddressService addressService;  
04. 
05.    public Address findAddress(String userName) {  
06.        return addressService.findAddress(userName);  
07.    }  
08. 
09.    public Iterator<Address> findAddresses(String userName) {  
10.        return addressService.findAddresses(userName);  
11.    }  
12.} 

我们有一个UserManager,要测试它的方法,但是,UserManager是依赖于AddressService的。这里我们准备mock掉AddressService。


第1章 jmock初体验

这个例子的作用在于像一个传统的hello world一样,给大家一个简明的介绍,可以有一个感觉,jmock可以做什么。
AddressService本身太复杂,很难构建,这个时候,jmock出场了。

Java代码 
01.@Test 
02.public void testFindAddress() {  
03. 
04.    // 建立一个test上下文对象。  
05.    Mockery context = new Mockery();  
06. 
07.    // 生成一个mock对象  
08.    final AddressService addressServcie = context  
09.            .mock(AddressService.class);  
10. 
11.    // 设置期望。  
12.    context.checking(new Expectations() {  
13.        {  
14.            // 当参数为"allen"的时候,addressServcie对象的findAddress方法被调用一次,并且返回西安。  
15.            oneOf(addressServcie).findAddress("allen");  
16.            will(returnValue(Para.Xian));  
17.        }  
18.    });  
19. 
20.    UserManager manager = new UserManager();  
21. 
22.    // 设置mock对象  
23.    manager.addressService = addressServcie;  
24. 
25.    // 调用方法  
26.    Address result = manager.findAddress("allen");  
27. 
28.    // 验证结果  
29.    Assert.assertEquals(Result.Xian, result);  
30. 
31.} 

那么这里做了什么事情呢?
1 首先,我们建立一个test上下文对象。
2 用这个mockery context建立了一个mock对象来mock AddressService.
3 设置了这个mock AddressService的findAddress应该被调用1次,并且参数为"allen"。
4 生成UserManager对象,设置addressService,调用findAddress。
5 验证期望被满足。

基本上,一个简单的jmock应用大致就是这样一个流程。

最显著的优点就是,我们没有AddressService的具体实现,一样可以测试对AddressService接口有依赖的其他类的行为。也就是说,我们通过mock一个对象来隔离这个对象对要测试的代码的影响。

由于大致的流程是一样的,我们提供一个抽象类来模板化jmock的使用。

Java代码 
01.public abstract class TestBase {  
02. 
03.    // 建立一个test上下文对象。  
04.    protected Mockery context = new Mockery();  
05. 
06.    // 生成一个mock对象  
07.    protected final AddressService addressServcie = context  
08.            .mock(AddressService.class);  
09. 
10.    /** 
11.     * 要测试的userManager. 
12.     * */ 
13.    protected UserManager manager;  
14. 
15.    /** 
16.     * 设置UserManager,并且设置mock的addressService。 
17.     * */ 
18.    private void setUpUserManagerWithMockAddressService() {  
19.        manager = new UserManager();  
20.        // 设置mock对象  
21.        manager.addressService = addressServcie;  
22.    }  
23. 
24.    /** 
25.     * 调用findAddress,并且验证返回值。 
26.     *  
27.     * @param userName 
28.     *            userName 
29.     * @param expected 
30.     *            期望返回的地址。 
31.     * */ 
32.    protected void assertFindAddress(String userName, Address expected) {  
33.        Address address = manager.findAddress(userName);  
34.        Assert.assertEquals(expected, address);  
35.    }  
36. 
37.    /** 
38.     * 调用findAddress,并且验证方法抛出异常。 
39.     * */ 
40.    protected void assertFindAddressFail(String userName) {  
41.        try {  
42.            manager.findAddress(userName);  
43.            Assert.fail();  
44.        } catch (Throwable t) {  
45.            // Nothing to do.  
46.        }  
47.    }  
48. 
49.    @Test 
50.    public final void test() {  
51. 
52.        setUpExpectatioin();  
53. 
54.        setUpUserManagerWithMockAddressService();  
55. 
56.        invokeAndVerify();  
57.    }  
58. 
59.    /** 
60.     * 建立期望。 
61.     * */ 
62.    protected abstract void setUpExpectatioin();  
63. 
64.    /** 
65.     * 调用方法并且验证结果。 
66.     * */ 
67.    protected abstract void invokeAndVerify();  
68.} 

这样一来,我们以后的例子中只用关心setUpExpectatioin()和invokeAndVerify()方法就好了。

第2章 期望

好了,让我们来看看一个期望的框架。

Java代码 
01.invocation-count (mock-object).method(argument-constraints);  
02.    inSequence(sequence-name);  
03.    when(state-machine.is(state-name));  
04.    will(action);  
05.    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代码 
01.@Override 
02.protected void setUpExpectatioin() {  
03.    context.checking(new Expectations() {  
04.        {  
05.            // 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。  
06.            allowing(addressServcie).findAddress("allen");  
07.            will(returnValue(Para.BeiJing));  
08. 
09.            // 当参数为null的时候,抛出IllegalArgumentException异常。  
10.            allowing(addressServcie).findAddress(null);  
11.            will(throwException(new IllegalArgumentException()));  
12.        }  
13.    });  
14.}  
15. 
16.@Override 
17.protected void invokeAndVerify() {  
18.    assertFindAddress("allen", Result.BeiJing);  
19.    assertFindAddressFail(null);  
20.} 


这里演示了两种调用方法的结果,返回值和抛异常。
使用jmock可以返回常量值,也可以根据变量生成返回值。
抛异常是同样的,可以模拟在不同场景下抛的各种异常。

对于Iterator的返回值,jmock也提供了特殊支持。

Java代码 
01.@Override 
02.protected void setUpExpectatioin() {  
03.    // 生成地址列表  
04.    final List<Address> addresses = new ArrayList<Address>();  
05.    addresses.add(Para.Xian);  
06.    addresses.add(Para.HangZhou);  
07. 
08.    final Iterator<Address> iterator = addresses.iterator();  
09. 
10.    // 设置期望。  
11.    context.checking(new Expectations() {  
12.        {  
13.            // 当参数为"allen"的时候,addressServcie对象的findAddresses方法用returnvalue返回一个Iterator<Address>对象。  
14.            allowing(addressServcie).findAddresses("allen");  
15.            will(returnValue(iterator));  
16. 
17.            // 当参数为"dandan"的时候,addressServcie对象的findAddresses方法用returnIterator返回一个Iterator<Address>对象。  
18.            allowing(addressServcie).findAddresses("dandan");  
19.            will(returnIterator(addresses));  
20.        }  
21.    });  
22. 
23.}  
24. 
25.@Override 
26.protected void invokeAndVerify() {  
27. 
28.    Iterator<Address> resultIterator = null;  
29. 
30.    // 第1次以"allen"调用方法  
31.    resultIterator = manager.findAddresses("allen");  
32.    // 断言返回的对象。  
33.    assertIterator(resultIterator);  
34. 
35.    // 第2次以"allen"调用方法,返回的与第一次一样的iterator结果对象,所以这里没有next了。  
36.    resultIterator = manager.findAddresses("allen");  
37.    Assert.assertFalse(resultIterator.hasNext());  
38. 
39.    // 第1次以"dandan"调用方法  
40.    resultIterator = manager.findAddresses("dandan");  
41.    // 断言返回的对象。  
42.    assertIterator(resultIterator);  
43. 
44.    // 第2次以"dandan"调用方法,返回的是一个全新的iterator。  
45.    resultIterator = manager.findAddresses("dandan");  
46.    // 断言返回的对象。  
47.    assertIterator(resultIterator);  
48.}  
49. 
50./** 断言resultIterator中有两个期望的Address */ 
51.private void assertIterator(Iterator<Address> resultIterator) {  
52.    Address address = null;  
53.    // 断言返回的对象。  
54.    address = resultIterator.next();  
55.    Assert.assertEquals(Result.Xian, address);  
56.    address = resultIterator.next();  
57.    Assert.assertEquals(Result.HangZhou, address);  
58.    // 没有Address了。  
59.    Assert.assertFalse(resultIterator.hasNext());  
60.} 

从这个例子可以看到对于Iterator,returnValue和returnIterator的不同。

Java代码 
01.@Override 
02.protected void setUpExpectatioin() {  
03.    // 设置期望。  
04.    context.checking(new Expectations() {  
05.        {  
06.            // 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。  
07.            allowing(addressServcie).findAddress("allen");  
08.            will(new Action() {  
09. 
10.                @Override 
11.                public Object invoke(Invocation invocation)  
12.                        throws Throwable {  
13.                    return Para.Xian;  
14.                }  
15. 
16.                @Override 
17.                public void describeTo(Description description) {  
18.                }  
19.            });  
20.        }  
21.    });  
22.}  
23. 
24.@Override 
25.protected void invokeAndVerify() {  
26.    assertFindAddress("allen", Result.Xian);  
27.} 


其实这里要返回一个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代码 
01.@Override 
02.protected void setUpExpectatioin() {  
03.    // 设置期望。  
04.    context.checking(new Expectations() {  
05.        {  
06.            // doAllAction  
07.            allowing(addressServcie).findAddress("allen");  
08.            will(doAll(returnValue(Para.Xian), returnValue(Para.HangZhou)));  
09. 
10.            // ActionSequence  
11.            allowing(addressServcie).findAddress("dandan");  
12.            will(onConsecutiveCalls(returnValue(Para.Xian),  
13.                    returnValue(Para.HangZhou)));  
14.        }  
15.    });  
16.}  
17. 
18.@Override 
19.protected void invokeAndVerify() {  
20.    assertFindAddress("allen", Result.HangZhou);  
21. 
22.    assertFindAddress("dandan", Result.Xian);  
23.    assertFindAddress("dandan", Result.HangZhou);  
24. 
25.} 



第4章 参数匹配

即设置argument-constraints

Java代码 
01.@Override 
02.protected void setUpExpectatioin() {  
03.    // 设置期望。  
04.    context.checking(new Expectations() {  
05.        {  
06.            // 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。  
07.            allowing(addressServcie).findAddress("allen");  
08.            will(returnValue(Para.Xian));  
09. 
10.            // 当参数为"dandan"的时候,addressServcie对象的findAddress方法返回一个Adress对象。  
11.            allowing(addressServcie).findAddress(with(equal("dandan")));  
12.            will(returnValue(Para.HangZhou));  
13. 
14.            // 当参数包含"zhi"的时候,addressServcie对象的findAddress方法返回一个Adress对象。  
15.            allowing(addressServcie).findAddress(  
16.                    with(new BaseMatcher<String>() {  
17. 
18.                        @Override 
19.                        public boolean matches(Object item) {  
20.                            String value = (String) item;  
21.                            if (value == null)  
22.                                return false;  
23.                            return value.contains("zhi");  
24.                        }  
25. 
26.                        @Override 
27.                        public void describeTo(Description description) {  
28.                        }  
29. 
30.                    }));  
31. 
32.            will(returnValue(Para.BeiJing));  
33. 
34.            // 当参数为其他任何值的时候,addressServcie对象的findAddress方法返回一个Adress对象。  
35.            allowing(addressServcie).findAddress(with(any(String.class)));  
36. 
37.            will(returnValue(Para.ShangHai));  
38.        }  
39.    });  
40. 
41.}  
42. 
43.@Override 
44.protected void invokeAndVerify() {  
45.    // 以"allen"调用方法  
46.    assertFindAddress("allen", Result.Xian);  
47.    // 以"dandan"调用方法  
48.    assertFindAddress("dandan", Result.HangZhou);  
49.    // 以包含"zhi"的参数调用方法  
50.    assertFindAddress("abczhidef", Result.BeiJing);  
51.    // 以任意一个字符串"abcdefg"调用方法  
52.    assertFindAddress("abcdefg", Result.ShangHai);  
53.} 

测试演示了直接匹配,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代码 
01.@Override 
02.protected void setUpExpectatioin() {  
03. 
04.    final Sequence sequence = context.sequence("mySeq_01");  
05. 
06.    // 设置期望。  
07.    context.checking(new Expectations() {  
08.        {  
09.            // 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。  
10.            oneOf(addressServcie).findAddress("allen");  
11.            inSequence(sequence);  
12.            will(returnValue(Para.Xian));  
13. 
14.            // 当参数为"dandan"的时候,addressServcie对象的findAddress方法返回一个Adress对象。  
15.            oneOf(addressServcie).findAddress("dandan");  
16.            inSequence(sequence);  
17.            will(returnValue(Para.HangZhou));  
18. 
19.        }  
20.    });  
21. 
22.}  
23. 
24.@Override 
25.protected void invokeAndVerify() {  
26.    assertFindAddress("allen", Result.Xian);  
27.    assertFindAddress("dandan", Result.HangZhou);  
28.} 

这里指定了调用的序列。使得调用必须以指定的顺序调用。
来看一个反例

Java代码 
01.@Override 
02.protected void setUpExpectatioin() {  
03. 
04.    final Sequence sequence = context.sequence("mySeq_01");  
05. 
06.    // 设置期望。  
07.    context.checking(new Expectations() {  
08.        {  
09.            // 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。  
10.            oneOf(addressServcie).findAddress("allen");  
11.            inSequence(sequence);  
12.            will(returnValue(Para.Xian));  
13. 
14.            // 当参数为"dandan"的时候,addressServcie对象的findAddress方法返回一个Adress对象。  
15.            oneOf(addressServcie).findAddress("dandan");  
16.            inSequence(sequence);  
17.            will(returnValue(Para.HangZhou));  
18. 
19.        }  
20.    });  
21.}  
22. 
23.@Override 
24.protected void invokeAndVerify() {  
25.    assertFindAddressFail("dandan");  
26.} 

当指定序列的第一个调用没有触发的时候,直接调用第2个,则会抛异常。
Note:指定序列的时候注意方法调用次数这个约束,如果是allowing那么在这个序列中,它是可以被忽略的。


第7章 状态机
状态机的作用在于模拟对象在什么状态下调用才用触发。

Java代码 
01.@Override 
02.protected void setUpExpectatioin() {  
03. 
04.    final States states = context.states("sm").startsAs("s1");  
05. 
06.    // 设置期望。  
07.    context.checking(new Expectations() {  
08.        {  
09.            // 状态为s1参数包含allen的时候返回西安  
10.            allowing(addressServcie).findAddress(  
11.                    with(StringContains.containsString("allen")));  
12.            when(states.is("s1"));  
13.            will(returnValue(Para.Xian));  
14. 
15.            // 状态为s1参数包含dandan的时候返回杭州,跳转到s2。  
16.            allowing(addressServcie).findAddress(  
17.                    with(StringContains.containsString("dandan")));  
18.            when(states.is("s1"));  
19.            will(returnValue(Para.HangZhou));  
20.            then(states.is("s2"));  
21. 
22.            // 状态为s2参数包含allen的时候返回上海  
23.            allowing(addressServcie).findAddress(  
24.                    with(StringContains.containsString("allen")));  
25.            when(states.is("s2"));  
26.            will(returnValue(Para.ShangHai));  
27.        }  
28.    });  
29.}  
30. 
31.@Override 
32.protected void invokeAndVerify() {  
33.    // s1状态  
34.    assertFindAddress("allen", Result.Xian);  
35.    assertFindAddress("allen0", Result.Xian);  
36. 
37.    // 状态跳转到 s2  
38.    assertFindAddress("dandan", Result.HangZhou);  
39. 
40.    // s2状态  
41.    assertFindAddress("allen", Result.ShangHai);  
42.} 

可以看到,如果序列一样,状态也为期望的执行设置了约束,这里就是用状态来约束哪个期望应该被执行。
可以用is或者isNot来限制状态。

状态机有一个很好的用处。
当我们建立一个test执行上下文的时候,如果建立的时候和执行的时候,我们都需要调用mock ojbect的方法,那么我们可以用状态机把这两部分隔离开。让他们在不同的状态下执行。

你可能感兴趣的:(jmock)