`
zhang_xzhi_xjtu
  • 浏览: 536466 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

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

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的方法,那么我们可以用状态机把这两部分隔离开。让他们在不同的状态下执行。
分享到:
评论
2 楼 RicardoX 2011-02-11  
写得非常好
1 楼 beike 2010-12-01  
收藏,对初学者 有帮助
居然和1.0差距这么大

好久没写mock test了...

相关推荐

    jmock-2.5.1-javadoc

    除了基本的模拟和期待,jMock还支持更复杂的场景,如顺序(顺序调用模拟对象的方法)、条件(某些方法的调用依赖于其他方法的调用结果)和回调(模拟对象方法的返回值取决于其他方法的调用)等。这些特性使得jMock...

    jMock基本使用方法

    ### jMock基本使用方法 #### 一、简介 jMock 是一个流行的 Java 框架,用于支持单元测试,特别是对于模拟对象(mock objects)的支持。通过 jMock,开发人员可以轻松地创建模拟对象来替代实际的对象依赖,从而在...

    Jmock2.6 jar包

    **Jmock2.6 Jar包**是Java编程领域中用于单元测试的重要库,它提供了模拟对象的功能,使得开发者能够在测试代码时对复杂依赖关系进行控制和隔离。在本文中,我们将深入探讨Jmock2.6的核心特性、使用场景以及如何在...

    JMock

    ### 基本使用 在JMock中,模拟对象通过`mock(Class&lt;T&gt; clazz)`方法创建,然后可以使用`expect`方法来指定其行为。例如,如果你有一个`Calculator`接口,可以这样创建并设置模拟对象: ```java Mockery context = ...

    jmock2.5.1.zip

    《jMock 2.5.1:模拟与测试的艺术》 在软件开发中,单元测试是确保代码质量的重要环节,而模拟(Mocking)技术则是单元测试中的关键工具。jMock,作为Java平台上的一个开源模拟框架,为开发者提供了一种高效、灵活...

    jmock cookbook 资源整合

    这本书详细讲解了各种场景下的最佳实践,涵盖了从基本概念到高级特性的全面内容。 无论是初学者还是经验丰富的测试工程师,都可以从这本书中获益。对于初学者,它提供了易于理解的示例和逐步指导;对于专家,它则...

    jmock-2.6.0-jars

    【jmock-2.6.0-jars】这个压缩包主要包含了`jmock`库的2.6.0版本的jar文件以及相关的源代码。`jmock`是Java平台上的一个模拟框架,它允许开发者在单元测试中创建和控制对象的行为,以便能够隔离测试并精确地指定期望...

    JMOCK 帮助 网页 文档

    **JMock 概述** JMock 是一个Java平台上的单元测试框架,专门用于模拟对象,以便在测试过程中控制和验证对象的行为。它基于EasyMock库,但提供了更强大的功能,尤其是对于处理复杂交互和顺序的场景。JMock使得...

    jmock-1.2.0-jars.zip

    这是JMock的核心库,提供了模拟对象的基本功能。它包括了定义和验证模拟对象行为的API,以及支持测试驱动开发(TDD)和行为驱动开发(BDD)的框架。在JMock中,你可以创建模拟对象来代替真实的对象,这些模拟对象会...

    JMOCK使用文档

    - `Jmock学习文档.docx`:更深入的JMock教程,可能包括高级用法和最佳实践。 - `Junit使用.ppt`:可能是一个关于JUnit基本用法和如何与JMock结合的演示文稿。 通过学习这些文档,开发者可以更全面地了解如何有效...

    jmock-1.2.0-jars.rar

    《JMock 1.2.0 - 模拟与测试的艺术》 JMock 是一个流行的 Java 开源库,专门用于创建和管理模拟对象,以便在单元测试中隔离被测代码。这个压缩包“jmock-1.2.0-jars.rar”包含的是 JMock 1.2.0 版本的 jar 文件,它...

    jmock2.5.1和easymock3.0

    而JMock和EasyMock则是两种广泛使用的Java单元测试框架,它们允许开发者模拟对象的行为和交互,以便于测试复杂的系统。本资源包含JMock 2.5.1和EasyMock 3.0的库文件,使得开发者在进行单元测试时无需再四处寻找相关...

    jmock-1.2.0.jar

    Maven-Central / jmock / jmock-cglib 1.2.0 Maven-Central / org.apache.activemq / activemq-ra 5.2.05.1.0 Maven-Central / org.apache.maven.shared / maven-dependency-tree 1.21.1 Maven-Central / org.apache...

    Jmock Mock 没有接口的类

    以下是如何使用 JMock 和 CGLIB 模拟无接口类的基本步骤: 1. 引入依赖:将这些 jar 文件添加到项目的类路径中,或者在使用 Maven 或 Gradle 的项目中,添加相应的依赖配置。 2. 创建模拟对象:使用 JMock 的 `...

    jmock jar包及doc文档

    本文将深入探讨JMock库,它是一个强大的Java模拟框架,常用于单元测试,以及如何结合Spring测试模块进行高效测试。 JMock是一个用于创建和验证对象行为的模拟框架,它允许开发者在测试中模拟对象的行为,而不是依赖...

    maven+jmock

    接下来,我们需要了解 JMock 的基本概念。JMock 提供了模拟(mock)、期待(expectation)和验证(verification)等机制。模拟对象可以代替真实的对象,提供预定义的返回值或行为。期待定义了在测试过程中模拟对象...

    软件测试资料集合,jtest,jmock

    `jmock-2.6.0-RC2-javadoc.zip`和`jmock-2.5.1-javadoc.zip`包含的是`jmock`的API文档,可以帮助开发者理解和使用其API。`jmock-2.6.0-RC2-jars.zip`和`jmock-2.5.1-jars.zip`则包含了相应的库文件,可以直接在项目...

Global site tag (gtag.js) - Google Analytics