原文: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的。

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

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

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

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

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

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

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

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

  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

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

  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. }

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

  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章 状态机 
状态机的作用在于模拟对象在什么状态下调用才用触发。

  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的方法,那么我们可以用状态机把这两部分隔离开。让他们在不同的状态下执行。

jmock2.5基本教程(转)的更多相关文章

  1. jmock2.5 基本教程

    目录 第0章 概述 第1章 jmock初体验 第2章 期望 第3章 返回值 第4章 参数匹配 第5章 指定方法调用次数 第6章 指定执行序列 第7章 状态机 第0章 概述 现在的dev不是仅仅要写co ...

  2. Angular2入门系列教程7-HTTP(一)-使用Angular2自带的http进行网络请求

    上一篇:Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数 感觉这篇不是很好写,因为涉及到网络请求,如果采用真实的网络请求,这个例子大家拿到手估计还要自己写一个web ...

  3. Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数

    上一篇:Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数 之前介绍了简单的路由以及传参,这篇文章我们将要学习复杂一些的路由以及传递其他附加参数.一个好的路由系统可以使我们 ...

  4. Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数

    上一篇:Angular2入门系列教程-服务 上一篇文章我们将Angular2的数据服务分离出来,学习了Angular2的依赖注入,这篇文章我们将要学习Angualr2的路由 为了编写样式方便,我们这篇 ...

  5. Angular2入门系列教程4-服务

    上一篇文章 Angular2入门系列教程-多个组件,主从关系 在编程中,我们通常会将数据提供单独分离出来,以免在编写程序的过程中反复复制粘贴数据请求的代码 Angular2中提供了依赖注入的概念,使得 ...

  6. Angular2入门系列教程1-使用Angular-cli搭建Angular2开发环境

    一直在学Angular2,百忙之中抽点时间来写个简单的教程. 2016年是前端飞速发展的一年,前端越来越形成了(web component)组件化的编程模式:以前Jquery通吃一切的田园时代一去不复 ...

  7. wepack+sass+vue 入门教程(三)

    十一.安装sass文件转换为css需要的相关依赖包 npm install --save-dev sass-loader style-loader css-loader loader的作用是辅助web ...

  8. wepack+sass+vue 入门教程(二)

    六.新建webpack配置文件 webpack.config.js 文件整体框架内容如下,后续会详细说明每个配置项的配置 webpack.config.js直接放在项目demo目录下 module.e ...

  9. wepack+sass+vue 入门教程(一)

    一.安装node.js node.js是基础,必须先安装.而且最新版的node.js,已经集成了npm. 下载地址 node安装,一路按默认即可. 二.全局安装webpack npm install ...

随机推荐

  1. [ES6] ITERATORS

    Iterables return an iterator object. This object knows how to access items from a collection 1 at a ...

  2. C#控制台吹泡泡算法

    代码如下: static void Main(string[] args) { Bubbling(100, 100, "O", 1000); Console.ReadLine(); ...

  3. 【转】iOS开发24:使用SQLite3存储和读取数据

    转自:http://my.oschina.net/plumsoft/blog/57626 SQLite3是嵌入在iOS中的关系型数据库,对于存储大规模的数据很有效.SQLite3使得不必将每个对象都加 ...

  4. 关于asp.net的<%#%>的一些总结

    一.说明 asp特有的控件在前台绑定数据的语法,且必须要调用该控件的DataBind()方法才执行,也可以整个页面数据绑定. 二.注意 1.并不是只有服务器控件才可以使用该语法,当整个页面调用this ...

  5. Memcached的一些知识

    一.内存分配在Memcached内存结构中有两个非常重要的概念:slab 和 chunk,我们先从下图中对这两个概念有一个感性的认识: memcached内存结构Slab是一个内存块,它是memcac ...

  6. MD5 Tool 工具类

    package com.example.secret.tools; import java.io.UnsupportedEncodingException; import java.security. ...

  7. Iterable 超级接口

    这是一个老祖宗,一代一代往下拨 collection 的方法如下,是一个跟接口方法如下,见API collection  : add():添加一个元素 addAll():添加一组元素 clear(); ...

  8. About USB Data Link Cable API

    About USB Data Link Cable API The text on this webpage is licensed under the Creative Commons Attrib ...

  9. [总结]Map: C++ V.S. Java

    整理一下Map在Java 和 C++的基本操作,欢迎大家一起交流学习. 附: 对于在C++中,选用map 还是 unordered_map,可以参考这篇讨论.相对简单粗暴的结论是,unordered_ ...

  10. 搭建hbase-0.94.26集群环境

    先安装hadoop1.2.1,见http://blog.csdn.net/jediael_lu/article/details/38926477 1.配置hbase-site.xml <prop ...