JMock的特性—自动化测试主流工具(5)

发表于:2020-5-22 13:51

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:51Testing教研团队    来源:51Testing软件测试网原创

  3.4.3 JMock的特性
  JMock利用多态性动态产生Mock对象,而无须专门创建Mock类,可以快速生成Mock对象,包括模拟接口的各种方法,传递不同的参数值和返回值。只需要在对某个方法进行测试前先动态模拟相应的被调方法即可。可以把Mock对象直接放在测试夹具中,并根据需要灵活地使用,特别是在需要模拟一系列返回值时,这将特别高效。
  JMock是专门针对Java程序进行单元测试的框架,与JUnit无缝集成,共同组成单元测试框架的黄金组合。总的来说,JMock是一个用于模拟对象技术的轻量级实现。JMock具有以下特点。
  (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软件测试网获得人民邮电出版社和作者授权连载本书部分章节。
任何个人或单位未获得明确的书面许可,不得对本文内容复制、转载或进行镜像,否则将追究法律责任。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号