3.4.3 JMock的特性
JMock利用多态性动态产生Mock对象,而无须专门创建Mock类,可以快速生成Mock对象,包括模拟接口的各种方法,传递不同的参数值和返回值。只需要在对某个方法进行测试前先动态模拟相应的被调方法即可。可以把Mock对象直接放在测试夹具中,并根据需要灵活地使用,特别是在需要模拟一系列返回值时,这将特别高效。
(1)可以用简单易行的方法定义模拟对象,无须破坏本来的代码结构表。
(2)可以定义对象之间的交互,从而增强测试的稳定性。
(3)可以集成到测试框架中。
(4)易扩充。
要使用JMock,必须首先在项目的类路径中导入Jar包,如jmock-2.5.1.jar和JMock与JUnit集成的JAR包(如jmock-junit4-2.5.1.jar)。
3.4.4 使用JMock模拟isNumber方法
仍然基于上述代码,这次不使用MockIString类来创建Mock类,而直接使用JMock来创建Mock类,示例代码(StringHandleJMock.Java)如图3-41所示。
package com.learn.jmock; import org.jmock.Expectations; import org.jmock.Mockery; import org.jmock.integration.junit4.JMock; import org.jmock.integration.junit4.JUnit4Mockery; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.*; import com.learn.updated.*; @RunWith(JMock.class) public class StringHandleJMock { public Mockery context = new JUnit4Mockery(); @Test public void splitString() { // 使用JMock final IString istring = context.mock(IString.class); final String source = "11,22,33,44"; // 参数值必须定义为常量 // 创建模拟方法的参数和期望结果 context.checking(new Expectations() {{ atLeast(3).of(istring).isNumber(with(any(String.class))); will(returnValue(true)); }}); StringHandle stringHandle = new StringHandle(); stringHandle.istring = istring; // 将Mock对象istring直接传递给StringHandle Integer[] expect = {11, 22, 33, 44}; Integer[] actual = stringHandle.splitString(source, ","); assertArrayEquals(expect, actual); } } |
图3-41 直接使用JMock来创建Mock类的示例代码
以上代码便是JMock的核心,代码演示了如何使用JMock来动态创建一个Mock类istring,并调用其方法isNumber,参数及返回值在context.checking中定义。可以看到诸如atLeast(3)、with(any(String.class))、will(returnValue(true))这样的语句。在JMock中,类似的关键方法还有很多,列举如下。
(1)可以指定期望调用次数的方法如下。
●one:仅调用一次。
●exactly(n).of:调用n次。
●atLeast(n).of:至少调用n次。
●atMost(n).of:至多调用n次。
●between(min,max).of:最少调用min次,最多调用max次。
●allowing:调用任意次。
●ignoring:不检查此Mock对象的调用。
●never:一次也不调用。
(2)可以作为参数匹配器的方法如下。
●equal(n):参数等于n。
●same(o):参数是对象且和对象o引用的是同一个对象。
●any(Class<T> type):参数是type类型的任意值。
●a(Class<T> type):表示type或者type子类的一个实例。
●an(Class<T> type):等同于a(Class<T> type)。
●aNull(Class<T> type):参数的类型为type,为空。
●aNonNull(Class<T> type):参数的类型为type,非空。
●not(m):对匹配规则取反,如not(equal(n)),参数不为n。
●anyOf(m1, m2, ..., mn):要和m1到mn中的任意一个匹配规则进行匹配。
●allOf(m1, m2, ..., mn):要和m1到mn的所有匹配规则进行匹配。
(3)定义返回值的方法如下。
●will(returnValue(v)):返回v给调用者。
●will(returnIterator(c)):每次调用时返回集合c的一个新迭代器。
●will(returnIterator(v1, v2, ..., vn)):每次调用都返回对应的迭代器,如第一次返回v1,第n次返回vn。
●will(throwException(e)):被调用后,抛出一个异常e。
●will(doAll(a1, a2, ..., an)):每次调用都会导致a1到an定义的操作发生,即操作是可以嵌套的。
如果期望返回值因调用的次数不同而不同,则可以为一个Mock方法提供多个返回值。示例代码如图3-42所示。
one(istring).isNumber(with(any(String.class))); will(returnValue(true)); one(istring).isNumber(with(any(String.class))); will(returnValue(false)); one(istring).isNumber(with(any(String.class))); will(returnValue(true)); |
图3-42 为一个Mock方法提供多个返回值的示例代码
图3-42所示代码表明第一次调用isNumber时返回true,第二次返回false,第三次返回true。然而,这样的书写方法不利于维护代码,所以JMock提供了一个更好的方法onConsecutiveCalls。示例代码如图3-43所示。
atLeast(3).of(istring).isNumber(with(any(String.class))); //will(returnValue(true)); will(onConsecutiveCalls(returnValue(true), returnValue(false), returnValue(true))); |
图3-43 关于onConsecutiveCalls的示例代码
3.4.5 使用JMock模拟类
因为JMock使用Java的默认反射能力,所以JMock框架的默认配置仅仅可以模拟接口,不能模拟类。另外,我们不提倡一个开发团队使用类而不使用接口,这本身就不是一种好的开发风格。然而,考虑到开发团队的人员水平良莠不齐,或者其他遗留原因等,难免不会存在直接在代码中实例化类并调用其方法的情况。JMock通过使用ClassImposteriser扩展类来使用CGLIB2.1和Objenesis类库,可以像接口一样创建类的模拟对象。当使用遗留代码时,这对分解紧耦合类之间的依赖关系是很有用的。
要使用JMock来模拟类,必须首先将jmock-legacy-2.5.1.jar、cglib-nodep-2.1_3.jar和objenesis-1.0.jar添加到项目的CLASSPATH中。
同样需要改造代码,以适应JMock的需要,而事实上,如果真的要修改代码,只能说明一件事,那就是代码本身在结构设计上存在问题。
首先,修改CompareHandle类的代码(见图3-19),删除方法mainCheck中的“ArrayHandle ah = new ArrayHandle();”一句,并为类添加定义“public ArrayHandle ah = null;”,需要调用的时候才将类的实例传递给mainCheck方法。
然后,使用JMock来模拟类ArrayHandle,并模拟arraySort和arrayCompare方法的返回值。示例代码如图3-44所示。
package com.learn.jmock; import static org.junit.Assert.assertEquals; import org.jmock.Mockery; import org.jmock.Expectations; import org.jmock.integration.junit4.JMock; import org.jmock.integration.junit4.JUnit4Mockery; import org.jmock.lib.legacy.ClassImposteriser; import org.junit.Test; import org.junit.runner.RunWith; import com.learn.compare.ArrayHandle; import com.learn.compare.CompareHandle; @RunWith(JMock.class) public class CompareHandleMockClass { Mockery context = new JUnit4Mockery() {{ setImposteriser(ClassImposteriser.INSTANCE); }}; @Test public void mainCheck() { final ArrayHandle ah = context.mock(ArrayHandle.class); final Integer[] a = {1, 2, 3}; final Integer[] b = {1, 3, 2}; context.checking(new Expectations() {{ atLeast(1).of(ah).arrayCompare(with(any(Integer[].class)), with(any(Integer[].class))); will(returnValue(false)); atLeast(1).of(ah).arraySort(with(any(Integer[].class))); will(returnValue(with(any(Integer[].class)))); }}); CompareHandle ch = new CompareHandle(); ch.ah = ah; assertEquals(3, ch.mainCheck(a, b)); } } |
图3-44 使用JMock模拟类ArrayHandle的示例代码
其实,通过对以上代码的仔细分析,你会发现,这些代码与JMock模拟接口没有本质上的区别。同样需要对被测代码进行良好的设计,否则仍然无法实现模拟。既然这样,那么为什么不使用多态呢?毕竟多态才是面向对象编程的核心。
版权声明:51Testing软件测试网获得人民邮电出版社和作者授权连载本书部分章节。
任何个人或单位未获得明确的书面许可,不得对本文内容复制、转载或进行镜像,否则将追究法律责任。