jmock2.5基本教程

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

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的。

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出场了。

@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接口有依赖的其他类的行为。也就是说,我们通过mock一个对象来隔离这个对象对要测试的代码的影响。 

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

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章 期望 

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

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)。

@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也提供了特殊支持。

@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的不同。

@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的使用。

@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

@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章 指定执行序列

@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() {   
    assertFindAddressFail("dandan");   
}  

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


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

@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基本教程)