jmock2.5基本教程

阅读更多

转载自 ---- http://zhang-xzhi-xjtu.iteye.com/blog/770438

 

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
     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
     addresses =  new  ArrayList
    ();  
  5.     addresses.add(Para.Xian);  
  6.     addresses.add(Para.HangZhou);  
  7.   
  8.     final  Iterator
     iterator = addresses.iterator();  
  9.   
  10.     // 设置期望。   
  11.     context.checking(new  Expectations() {  
  12.         {  
  13.             // 当参数为"allen"的时候,addressServcie对象的findAddresses方法用returnvalue返回一个Iterator
    对象。   
  14.             allowing(addressServcie).findAddresses("allen" );  
  15.             will(returnValue(iterator));  
  16.   
  17.             // 当参数为"dandan"的时候,addressServcie对象的findAddresses方法用returnIterator返回一个Iterator
    对象。   
  18.             allowing(addressServcie).findAddresses("dandan" );  
  19.             will(returnIterator(addresses));  
  20.         }  
  21.     });  
  22.   
  23. }  
  24.   
  25. @Override   
  26. protected   void  invokeAndVerify() {  
  27.   
  28.     Iterator
     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
     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() {  
  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的方法,那么我们可以用状态机把这两部分隔离开。让他们在不同的状态下执行。

你可能感兴趣的:(java,jmock)