jmock通过mock对象来模拟一个对象的行为,从而隔离开我们不关心的其他对象,使得UT的编写变得更为可行,也使得TDD变得更为方便,自然而然的,也就成为敏捷开发的一个利器。
class"org.hamcrest.TypeSafeMatcher"'ssignerinformationdoesnotmatchsignerinformationofotherclassesinthesamepackage。
findAddresses(String userName) { 10. return addressService.findAddresses(userName);
11. }
12.}
我们有一个UserManager,要测试它的方法,但是,UserManager是依赖于AddressService的。
这里我们准备mock掉AddressService。
第1章jmock初体验
这个例子的作用在于像一个传统的helloworld一样,给大家一个简明的介绍,可以有一个感觉,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用这个mockerycontext建立了一个mock对象来mockAddressService.
3设置了这个mockAddressService的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-objectmock对象
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.check