zoukankan      html  css  js  c++  java
  • jmock2.5基本教程 转载

    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代码 复制代码 收藏代码
    1. public class UserManager {   
    2.   
    3.     public AddressService addressService;   
    4.   
    5.     public Address findAddress(String userName) {   
    6.         return addressService.findAddress(userName);   
    7.     }   
    8.   
    9.     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代码 复制代码 收藏代码
    1. @Test  
    2. public void testFindAddress() {   
    3.   
    4.     // 建立一个test上下文对象。   
    5.     Mockery context = new Mockery();   
    6.   
    7.     // 生成一个mock对象   
    8.     final AddressService addressServcie = context   
    9.             .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代码 复制代码 收藏代码
    1. public abstract class TestBase {   
    2.   
    3.     // 建立一个test上下文对象。   
    4.     protected Mockery context = new Mockery();   
    5.   
    6.     // 生成一个mock对象   
    7.     protected final AddressService addressServcie = context   
    8.             .mock(AddressService.class);   
    9.   
    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代码 复制代码 收藏代码
    1. invocation-count (mock-object).method(argument-constraints);   
    2.     inSequence(sequence-name);   
    3.     when(state-machine.is(state-name));   
    4.     will(action);   
    5.     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代码 复制代码 收藏代码
    1. @Override  
    2. protected void setUpExpectatioin() {   
    3.     context.checking(new Expectations() {   
    4.         {   
    5.             // 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。   
    6.             allowing(addressServcie).findAddress("allen");   
    7.             will(returnValue(Para.BeiJing));   
    8.   
    9.             // 当参数为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代码 复制代码 收藏代码
    1. @Override  
    2. protected void setUpExpectatioin() {   
    3.     // 生成地址列表   
    4.     final List<Address> addresses = new ArrayList<Address>();   
    5.     addresses.add(Para.Xian);   
    6.     addresses.add(Para.HangZhou);   
    7.   
    8.     final Iterator<Address> iterator = addresses.iterator();   
    9.   
    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代码 复制代码 收藏代码
    1. @Override  
    2. protected void setUpExpectatioin() {   
    3.     // 设置期望。   
    4.     context.checking(new Expectations() {   
    5.         {   
    6.             // 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。   
    7.             allowing(addressServcie).findAddress("allen");   
    8.             will(new Action() {   
    9.   
    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代码 复制代码 收藏代码
    1. @Override  
    2. protected void setUpExpectatioin() {   
    3.     // 设置期望。   
    4.     context.checking(new Expectations() {   
    5.         {   
    6.             // doAllAction   
    7.             allowing(addressServcie).findAddress("allen");   
    8.             will(doAll(returnValue(Para.Xian), returnValue(Para.HangZhou)));   
    9.   
    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代码 复制代码 收藏代码
    1. @Override  
    2. protected void setUpExpectatioin() {   
    3.     // 设置期望。   
    4.     context.checking(new Expectations() {   
    5.         {   
    6.             // 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。   
    7.             allowing(addressServcie).findAddress("allen");   
    8.             will(returnValue(Para.Xian));   
    9.   
    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代码 复制代码 收藏代码
    1. @Override  
    2. protected void setUpExpectatioin() {   
    3.   
    4.     final Sequence sequence = context.sequence("mySeq_01");   
    5.   
    6.     // 设置期望。   
    7.     context.checking(new Expectations() {   
    8.         {   
    9.             // 当参数为"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代码 复制代码 收藏代码
    1. @Override  
    2. protected void setUpExpectatioin() {   
    3.   
    4.     final Sequence sequence = context.sequence("mySeq_01");   
    5.   
    6.     // 设置期望。   
    7.     context.checking(new Expectations() {   
    8.         {   
    9.             // 当参数为"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代码 复制代码 收藏代码
    1. @Override  
    2. protected void setUpExpectatioin() {   
    3.   
    4.     final States states = context.states("sm").startsAs("s1");   
    5.   
    6.     // 设置期望。   
    7.     context.checking(new Expectations() {   
    8.         {   
    9.             // 状态为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的方法,那么我们可以用状态机把这两部分隔离开。让他们在不同的状态下执行。

  • 相关阅读:
    Linux(CentOS)下安装OMNet++
    Linux(CentOS)安装JDK
    给电脑安装Linux系统(CentOS)
    OmNet++遇到的问题
    数论倒数总结
    [AHOI2007]密码箱
    [AHOI2005]约数研究
    Spark scala groupBy后求和
    Scala Seq创建简单的Spark DataFrame
    Spark DataFrame分组后选取第一行
  • 原文地址:https://www.cnblogs.com/zfc2201/p/2307970.html
Copyright © 2011-2022 走看看