• jmock2.5 基本教程


    目录 
    0章 概述 
    1jmock初体验 
    2章 期望 
    3章 返回值 
    4章 参数匹配 
    5章 指定方法调用次数 
    6章 指定执行序列 
    7章 状态机 

    0章 概述 

    现在的dev不是仅仅要写code而已,UT已经变为开发中不可缺少的一环。JUnit的出现给javaerUT编写提供了巨大的便利。但是JUnit并没有解决所有的问题。 
    当我们要测试一个功能点的时候,需要把不需要我们关注的东西隔离开,从而可以只关注我们需要关注的行为。 
    jmock通过mock对象来模拟一个对象的行为,从而隔离开我们不关心的其他对象,使得UT的编写变得更为可行,也使得TDD变得更为方便,自然而然的,也就成为敏捷开发的一个利器。 

    可以到http://www.jmock.org/download.html下载jmock. 
    添加jarclasspath 
    添加的时候,注意把JUnit4order放到最后。因为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. publicclass UserManager {   
      2. public AddressService addressService;   
      3.   public Address findAddress(String userName) {   
      4.     return addressService.findAddress(userName);   
      5.   }   
      6.   public Iterator<Address> findAddresses(String userName) {   
      7.     return addressService.findAddresses(userName);   
      8.   }   
      9. }  

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


    1jmock初体验 

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

    Java代码  

      1. @Test  
      2. publicvoid testFindAddress() {   
      3.   // 建立一个test上下文对象。 
      4.   Mockery context = new Mockery();   
      5.   // 生成一个mock对象 
      6.   final AddressService addressServcie = context   
      7.   .mock(AddressService.class);   
      8.   // 设置期望。 
      9.   context.checking(new Expectations() {   
      10.   {   
      11.   // 当参数为"allen"的时候,addressServcie对象的findAddress方法被调用一次,并且返回西安。 
      12.   oneOf(addressServcie).findAddress("allen");   
      13.   will(returnValue(Para.Xian));   
      14.   }   
      15.   });   
      16.   UserManager manager = new UserManager();   
      17.   // 设置mock对象 
      18.   manager.addressService = addressServcie;   
      19.   // 调用方法 
      20.   Address result = manager.findAddress("allen");   
      21.   // 验证结果 
      22.   Assert.assertEquals(Result.Xian, result);   
      23. }  

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

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

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

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

    Java代码  

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


    这样一来,我们以后的例子中只用关心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. // 当参数为null的时候,抛出IllegalArgumentException异常。   
    9. allowing(addressServcie).findAddress(null);   
    10. will(throwException(new IllegalArgumentException()));   
    11. }   
    12. });   
    13. }   
    14. @Override  
    15. protected void invokeAndVerify() {   
    16. assertFindAddress("allen", Result.BeiJing);   
    17. assertFindAddressFail(null);   
    18. }  



    这里演示了两种调用方法的结果,返回值和抛异常。 
    使用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. final Iterator<Address> iterator = addresses.iterator();   
    8. // 设置期望。   
    9. context.checking(new Expectations() {   
    10. {   
    11. // 当参数为"allen"的时候,addressServcie对象的findAddresses方法用returnvalue返回一个Iterator<Address>对象。   
    12. allowing(addressServcie).findAddresses("allen");   
    13. will(returnValue(iterator));   
    14. // 当参数为"dandan"的时候,addressServcie对象的findAddresses方法用returnIterator返回一个Iterator<Address>对象。   
    15. allowing(addressServcie).findAddresses("dandan");   
    16. will(returnIterator(addresses));   
    17. }   
    18. });   
    19. }   
    20. @Override  
    21. protected void invokeAndVerify() {   
    22. Iterator<Address> resultIterator = null;   
    23. // 1次以"allen"调用方法   
    24. resultIterator = manager.findAddresses("allen");   
    25. // 断言返回的对象。   
    26. assertIterator(resultIterator);   
    27. // 2次以"allen"调用方法,返回的与第一次一样的iterator结果对象,所以这里没有next了。   
    28. resultIterator = manager.findAddresses("allen");   
    29. Assert.assertFalse(resultIterator.hasNext());   
    30. // 1次以"dandan"调用方法   
    31. resultIterator = manager.findAddresses("dandan");   
    32. // 断言返回的对象。   
    33. assertIterator(resultIterator);   
    34. // 2次以"dandan"调用方法,返回的是一个全新的iterator   
    35. resultIterator = manager.findAddresses("dandan");   
    36. // 断言返回的对象。   
    37. assertIterator(resultIterator);   
    38. }   
    39. /** 断言resultIterator中有两个期望的Address */  
    40. private void assertIterator(Iterator<Address> resultIterator) {   
    41. Address address = null;   
    42. // 断言返回的对象。   
    43. address = resultIterator.next();   
    44. Assert.assertEquals(Result.Xian, address);   
    45. address = resultIterator.next();   
    46. Assert.assertEquals(Result.HangZhou, address);   
    47. // 没有Address了。   
    48. Assert.assertFalse(resultIterator.hasNext());   
    49. }  


    从这个例子可以看到对于IteratorreturnValuereturnIterator的不同。

    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. @Override  
    10. public Object invoke(Invocation invocation)   
    11. throws Throwable {   
    12.   return Para.Xian;   
    13. }   
    14. @Override  
    15. public void describeTo(Description description) {   
    16.   }   
    17. });   
    18. }   
    19.   });   
    20. }   
    21. @Override  
    22. protected void invokeAndVerify() {   
    23.   assertFindAddress("allen", Result.Xian);   
    24. }  



    其实这里要返回一个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 

    举个例子来说明DoAllActionActionSequence的使用。

    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.   // ActionSequence   
    10.   allowing(addressServcie).findAddress("dandan");   
    11.   will(onConsecutiveCalls(returnValue(Para.Xian),   
    12.   returnValue(Para.HangZhou)));   
    13.   }   
    14.   });   
    15. }   
    16. @Override  
    17. protected void invokeAndVerify() {   
    18.   assertFindAddress("allen", Result.HangZhou);   
    19.   assertFindAddress("dandan", Result.Xian);   
    20.   assertFindAddress("dandan", Result.HangZhou);   
    21. }  




    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. // 当参数为"dandan"的时候,addressServcie对象的findAddress方法返回一个Adress对象。   
    10. allowing(addressServcie).findAddress(with(equal("dandan")));   
    11. will(returnValue(Para.HangZhou));   
    12. // 当参数包含"zhi"的时候,addressServcie对象的findAddress方法返回一个Adress对象。   
    13. allowing(addressServcie).findAddress(   
    14. with(new BaseMatcher<String>() {   
    15. @Override  
    16. public boolean matches(Object item) {   
    17.   String value = (String) item;   
    18.   if (value == null)   
    19.     return false;   
    20.   return value.contains("zhi");   
    21. }   
    22. @Override  
    23. public void describeTo(Description description) {   
    24. }   
    25. }));   
    26.   
    27. will(returnValue(Para.BeiJing));   
    28.   // 当参数为其他任何值的时候,addressServcie对象的findAddress方法返回一个Adress对象。   
    29.   allowing(addressServcie).findAddress(with(any(String.class)));   
    30.   will(returnValue(Para.ShangHai));   
    31.   }   
    32.   });   
    33. }   
    34. @Override  
    35. protected void invokeAndVerify() {   
    36.   // "allen"调用方法   
    37.   assertFindAddress("allen", Result.Xian);   
    38.   // "dandan"调用方法   
    39.   assertFindAddress("dandan", Result.HangZhou);   
    40.   // 以包含"zhi"的参数调用方法   
    41.   assertFindAddress("abczhidef", Result.BeiJing);   
    42.   // 以任意一个字符串"abcdefg"调用方法   
    43.   assertFindAddress("abcdefg", Result.ShangHai);   
    44. }  


    测试演示了直接匹配,equal匹配,自定义匹配,任意匹配。 
    其实,这些都是为了给参数指定一个Matcher,来决定调用方法的时候,是否接收这个参数。 
    Expectations中提供了一些便利的方法方便我们构造Matcher. 
    其中 
    equal判断用equal方法判断是否相等。 
    same判断是否是同一个引用。 
    anyanything接收任意值。 
    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都是很明了的。只有allowingignoring比较特殊,这两个的实际效果是一样的,但是关注点不一样。当我们允许方法可以任意次调用时,用allowing,当我们不关心一个方法的调用时,用ignoring 

    6章 指定执行序列

    Java代码  

    1. @Override  
    2. protected void setUpExpectatioin() {   
    3. final Sequence sequence = context.sequence("mySeq_01");   
    4.   // 设置期望。   
    5.   context.checking(new Expectations() {   
    6.   {   
    7.   // 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。   
    8.   oneOf(addressServcie).findAddress("allen");   
    9.   inSequence(sequence);   
    10.   will(returnValue(Para.Xian));   
    11.   // 当参数为"dandan"的时候,addressServcie对象的findAddress方法返回一个Adress对象。   
    12.   oneOf(addressServcie).findAddress("dandan");   
    13.   inSequence(sequence);   
    14.   will(returnValue(Para.HangZhou));   
    15.   }   
    16.   });   
    17. }   
    18. @Override  
    19. protected void invokeAndVerify() {   
    20.   assertFindAddress("allen", Result.Xian);   
    21.   assertFindAddress("dandan", Result.HangZhou);   
    22. }  


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

    Java代码  

    1. @Override  
    2. protected void setUpExpectatioin() {   
    3.   final Sequence sequence = context.sequence("mySeq_01");   
    4.   // 设置期望。   
    5.   context.checking(new Expectations() {   
    6.   {   
    7.   // 当参数为"allen"的时候,addressServcie对象的findAddress方法返回一个Adress对象。   
    8.   oneOf(addressServcie).findAddress("allen");   
    9.   inSequence(sequence);   
    10.   will(returnValue(Para.Xian));   
    11.   // 当参数为"dandan"的时候,addressServcie对象的findAddress方法返回一个Adress对象。   
    12.   oneOf(addressServcie).findAddress("dandan");   
    13.   inSequence(sequence);   
    14.   will(returnValue(Para.HangZhou));   
    15.   }   
    16.   });   
    17. }   
    18. @Override  
    19. protected void invokeAndVerify() {   
    20.   assertFindAddressFail("dandan");   
    21. }  


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


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

    Java代码  

    1. @Override  
    2. protected void setUpExpectatioin() {   
    3. final States states = context.states("sm").startsAs("s1");   
    4.   // 设置期望。   
    5.   context.checking(new Expectations() {   
    6.   {   
    7.     // 状态为s1参数包含allen的时候返回西安   
    8.     allowing(addressServcie).findAddress(   
    9.   with(StringContains.containsString("allen")));   
    10.   when(states.is("s1"));   
    11.   will(returnValue(Para.Xian));   
    12.   // 状态为s1参数包含dandan的时候返回杭州,跳转到s2   
    13.   allowing(addressServcie).findAddress(   
    14.     with(StringContains.containsString("dandan")));   
    15.   when(states.is("s1"));   
    16.     will(returnValue(Para.HangZhou));   
    17.     then(states.is("s2"));   
    18.     // 状态为s2参数包含allen的时候返回上海   
    19.     allowing(addressServcie).findAddress(   
    20.       with(StringContains.containsString("allen")));   
    21.     when(states.is("s2"));   
    22.       will(returnValue(Para.ShangHai));   
    23.     }   
    24.     });   
    25.   }   
    26. @Override  
    27. protected void invokeAndVerify() {   
    28.   // s1状态   
    29.   assertFindAddress("allen", Result.Xian);   
    30.   assertFindAddress("allen0", Result.Xian);   
    31.   // 状态跳转到 s2   
    32.   assertFindAddress("dandan", Result.HangZhou);   
    33.   // s2状态   
    34.   assertFindAddress("allen", Result.ShangHai);   
    35. }  


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

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

    JMock在Junit4中的应用

    @RunWith(JMockit.class)
    public class DemoTest {
    @Tested
    private TPPPlayScheduleAPIImpl tPPPlayScheduleAPIImpl;

    @Injectable
    private PlayScheduleRateService playScheduleRateservice;

    @Test
    public void testGetAllPlayScheduleByCinemaId() {
    new Expectations() {
    {
    Map<Long, Map<String, PlayScheduleRate>> resultMap = new HashMap<Long, Map<String, PlayScheduleRate>>();
    Map<String, PlayScheduleRate> playScheduleRateMap = new HashMap<String, PlayScheduleRate>();
    Map<Integer, Integer> playNumCheckTypeMap = new HashMap<Integer, Integer>();
    PlayScheduleRate playScheduleRate = new PlayScheduleRate();
    playScheduleRate.setPlayNumCheckTypeMap(playNumCheckTypeMap);
    Map<String, Integer> scheduleRateMap = new HashMap<String, Integer>();
    mockScheduleRateMap(scheduleRateMap);
    playScheduleRate.setScheduleRateMap(scheduleRateMap);
    playScheduleRateMap.put("2017-02-23",playScheduleRate);
    playNumCheckTypeMap.put(1,99);
    playNumCheckTypeMap.put(2,98);
    resultMap.put(25L, playScheduleRateMap);
    playScheduleRateservice.queryAllPlayScheduleRateByCinemaId(anyLong);
    result = resultMap;
    }
    };
    ResultMapModel<Long, Map<String, PlayScheduleRate>> resultMapModel = tPPPlayScheduleAPIImpl.getAllPlayScheduleByCinemaId(25L);
    Assert.assertNotNull(resultMapModel);
    Map<Long, Map<String, PlayScheduleRate>> playScheduleRateMap = resultMapModel.getReturnValue();
    Assert.assertNotNull(playScheduleRateMap);
    }

  • 相关阅读:
    HERO 3
    office的一些应用,
    网页之间的参数传弟
    一个好的数码网站
    C++遍历中删除std::hash_map元素问题
    【转】Asio与shared_ptr的一些注意事项
    delphi的字节对齐
    paypal的即时付款通知参数列表(PDT)
    vs2010下libevent的使用
    mysql 数据库 left join,right join, inner join 知识
  • 原文地址:https://www.cnblogs.com/dengshihuang/p/7904259.html
Copyright © 2020-2023  润新知