1、ServiceClass.javaCollaborator.javaICollaborator.javaServiceClassTest.java待测试的类将是 ServiceClass,该类包含了一个方法:runService()。服务方法将获取实现简单接口 ICollaborator 的 Collaborator 对象。具体的 Collaborator 类中实现了一个方法:executeJob()。Collaborator 是必须正确模拟的类。第四个类是测试类:ServiceClassTest(实现的性质已经被尽可能地简化)。清单 1 将显示第四个类的代码。清单 1. 服务类的样例代码pu
2、blic class ServiceClass public ServiceClass() /no-args constructor public boolean runService(ICollaborator collaborator) if(success.equals(collaborator.executeJob() return true; else return false;在 ServiceClass 类中,if.else 代码块是一个简单的逻辑分支,根据测试期望说明选取一条路经 而不是另一条路经 之后测试将失败(或通过)的原因。下面显示了 Collaborator 类的源代码
3、。清单 2. Collaborator 类的样例代码public class Collaborator implements ICollaborator public Collaborator() /no-args constructor public String executeJob() return ;Collaborator 类也十分简单,它配有无参数的构造函数以及从 executeJob() 方法返回的简单 String。下面的代码显示了 ICollaborator 类的代码。public interface ICollaborator public abstract String
4、executeJob();接口 ICollaborator 有一个必须在 Collaborator 类中实现的方法。以上代码就绪后,让我们继续检验怎样在各种场景中成功地运行 ServiceClass 类的测试。回页首场景 1:使用 jMock 模拟接口测试 ServiceClass 类中的服务方法十分简单。假定测试要求为证明 runService() 方法并未运行 换言之,返回的布尔结果是 false。在这种情况下,传递给 runService() 方法的 ICollaborator 对象被模拟 为期望调用 executeJob() 方法,并返回除了 “success” 以外的字符串。通过这种
5、方法,确保把布尔字符串 false 返回给测试。下面所示的是包含测试逻辑的 ServiceClassTest 类代码。清单 3. 场景 1 的 ServiceClassTest 类样例代码import org.jmock.Mock;import org.jmock.cglib.MockObjectTestCase;public class ServiceClassTest extends MockObjectTestCase private ServiceClass serviceClass; private Mock mockCollaborator; private ICollaborat
6、or collaborator; public void setUp() serviceClass = new ServiceClass(); mockCollaborator = new Mock(ICollaborator.class); public void testRunServiceAndReturnFalse() mockCollaborator.expects(once().method (executeJob).will(returnValue(failure); collaborator = (ICollaborator)mockCollaborator.proxy();
7、boolean result = serviceClass.runService(collaborator); assertFalse(result);编写测试的时机用测试模拟框架运行您自己的测试的最佳方法是利用 test-first 灵活方法。首先创建测试并设定期望。仅在测试失败后才编写实现以修正测试。当测试运行正常时,您将编写另一个测试以检查稍后添加到待测试的类中的功能。如果将在各种测试用例中执行公共操作,则在测试中包括 setUp() 方法是一种很好的想法。包括 tearDown() 方法也很不错,但不作严格要求,除非要运行集成测试。另请注意,使用 jMock 和 RMock,框架将在测
8、试运行结束时或测试运行期间在所有模拟对象中检查所有期望。并不实际需要为每个模拟期望包括 verify() 方法。当作为 JUnit 测试运行时,测试将通过,如下所示:图 3. 场景 1 测试通过ServiceTestClass 类将扩展 jMock CGLIB 的 org.jmock.cglib.MockObjectTestCase 类。mockCollaborator 是一个十分简单的 org.jmock.JMock 类。通常,用 jMock 生成模拟对象有两种方法:要模拟接口,则使用 new Mock(Class.class) 方法要模拟具体类,则使用 mock(Class.class,
9、identifier) 方法必须注意的是怎样将模拟代理 传递给 ServiceClass 类中的 runService() 方法。使用 jMock,您可以从已创建的模拟对象(其中期望已经被设定)中提取代理实现。这一点在本文稍后的场景中至关重要,尤其是在涉及 RMock 的场景中。回页首场景 2:使用 jMock 模拟带有默认构造函数的具体类假定 ServiceClass 类中的 runService() 方法仅接受 Collaborator 类的具体实现。jMock 能够确保先前的测试通过而无需 更改期望吗?是的,只要您能够构造简单默认样式的 Collaborator 类。更改 Service
10、Class 类中的 runService() 方法使其反映以下代码。清单 4. 经过编辑的场景 2 的 ServiceClass 类public class ServiceClass public boolean runService(Collaborator collaborator) elseServiceClass 类的 if.else 逻辑分支保持不变(为了清晰起见)。同时,无参数构造函数仍然适用。注,并不总是需要有创造性逻辑,例如 while.do 子句或 for 循环来正确地测试类的方法。只要有针对类使用的对象的方法执行,简单的模拟期望就足以测试那些执行。您还必须更改 Servic
11、eClassTest 类以匹配场景,如下所示:清单 5. 经过编辑的场景 2 的 ServiceClassTest 类.private ServiceClass serviceClass; private Collaborator collaborator; mockCollaborator = mock(Collaborator.class, mockCollaborator); mockCollaborator.expects(once().method( collaborator = (Collaborator)mockCollaborator.proxy();这里有几点需要注意。第一,
12、runService() 方法签名已经不同于以往。它现在不接受 ICollaborator 接口,而接受具体类实现(Collaborator 类)。就测试框架而言,此更改非常重大(注,虽然在本质上反对多态,但是我们将使用传递具体类的示例(仅供举例之用)。在实际的面向对象的场景中绝对不能这样做)。第二,模拟 Collaborator 类的方式已经更改。使用 jMock CGLIB 库可以模拟具体类实现。提供给 jMock CGLIB 的 mock() 方法的附加 String 参数被用作创建的模拟对象的标识符。使用 jMock(当然,还有 RMock)时,在单一测试用例内每个模拟对象设置都要求有
13、惟一标识符。这对于在公共的 setUp() 方法中或在实际测试方法内定义的模拟对象来说是正确的。第三,测试方法的原始期望并未更改。仍然要求有 false 证明才能使测试通过。这是十分重要的,因为通过展示使用的测试框架足够灵活、可以适应各种输入带来的更改、同时仍然允许获得不变的测试结果,使它们在无法调节输入生成同样的结果时展示了其实际限制。现在,重新运行作为 JUnit 测试的测试。测试将通过,如下所示:图 4. 场景 2 测试通过在下一个场景中,情况会变得略微复杂一些。您将使用 RMock 框架来相对缓解一下这种困难的情形。回页首场景 3:使用 jMock 和 RMock 模拟带有非默认构造函
14、数的具体类首先像以前一样尝试使用 jMock 来模拟 Collaborator 对象 只是这一次,Collaborator 没有默认的无参数构造函数。注,保留布尔 false 结果的测试期望。同时假定 Collaborator 对象要求使用字符串和原始的 int 作为传递给构造函数的参数。清单 6 显示了对 Collaborator 对象所做的更改。清单 6. 经过编辑的场景 3 的 Collaborator 类public class Collaborator private String collaboratorString; private int collaboratorInt; pu
15、blic Collaborator(String string, int number) collaboratorString = string; collaboratorInt = number;Collaborator 类构造函数仍然十分简单。用传入参数设定类字段。这里不必使用任何其他逻辑,并且其 executeJob() 函数保持不变。重新运行测试,并且示例的所有其他组件保持不变。结果是灾难性的测试失败,如下所示:图 5. 场景 3 测试失败以上测试是作为简单的 JUnit 测试运行的,没有代码覆盖。您可以用大多数代码覆盖工具(例如,Cobertura 或 EclEmma)来运行本文中列
16、出的任何一个测试。但是,用 Eclipse 内的代码覆盖工具运行 RMock 测试时会带来一些问题(参见 表 1)。以下代码显示了实际堆栈跟踪的代码片段。清单 7. 场景 3 中测试失败的堆栈跟踪.Superclass has no null constructors but no arguments were givenat net.sf.cglib.proxy.Enhancer.emitConstructors(Enhancer.java:718)at net.sf.cglib.proxy.Enhancer.generateClass(Enhancer.java:499)at net.sf
17、.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216)at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285)660) . .失败原因是 jMock
18、无法通过没有无参数构造函数的类定义创建可行的模拟对象。实例化 Collaborator 对象的惟一方法是提供两个简单参数。您现在必须找到一种方法把参数提供给模拟对象实例化过程以达到同样的效果,这就是使用 RMock 的原因。用 RMock 测试框架更正失败的测试要更正测试,必须执行一些修改。这些更改可能显得十分重要,但是实际上,它们是一种相对简单的解决方法,利用两种框架的强大功能来实现目的。必需的第一项更改是使测试类成为 RMock TestCase,而不是成为 jMock CGLIB TestCase。目的是在测试本身内启用属于 RMock 的那些模拟对象的较容易的配置并且 更重要的是 在最
19、初设置期间启用这些配置。经验证明,如果测试类扩展的整个 TestCase 对象属于 RMock,则通过两个框架构造和使用模拟对象将更容易。此外,乍看之下,快速确定模拟对象的流程将更容易一些(在这里,流程 用于描述使用模拟对象作为参数甚或作为其他模拟对象的返回类型的情况)。必需的第二项更改是(至少)构造一个保存传递给 Collaborator 类的构造函数的参数实际值的对象数组。为了清晰起见,还可以包括构造函数接受的类类型的类型数组并可以传递该数组,以及刚刚描述为参数的对象数组以实例化模拟 Collaborator 对象。第三项更改涉及用正确语法构造对 RMock 模拟对象的一个或多个期望。而第
20、四项也是最后一项必需的更改是使 RMock 模拟对象脱离记录状态转入就绪状态。实现 RMock 更改清单 9 显示了对 ServiceClassTest 类的最终修改。它还显示了 RMock 及其相关功能的引入。清单 9. 修正场景 3 的 ServiceClassTest 类.import com.agical.rmock.extension.junit.RMockTestCase;public class ServiceClassTest extends RMockTestCase Object objectArray = new ObjectexampleString, 5; colla
21、borator =(Collaborator)intercept(Collaborator.class, objectArray, collaborator.executeJob(); modify().returnValue( startVerification();首先,需要注意测试的期望仍未改变。RMockTestCase 类的导入预示着引入 RMock 框架功能。接下来,测试类现在将扩展 RMockTestCase,而不是 MockObjectTestCase。稍后,我将向您展示在 TestClass 对象仍为 RMockTestCase 类型的对象的测试用例中重新引入 MockObj
22、ectTestCase。使用 intercept() 方法的备选方法通过 RMock,您可以使用 intercept() 方法仅模拟具体类。可以使用 RMock mock() 方法模拟具体类和接口。仅当需要模拟那几个重要方法时,使用 interface() 方法。此方法被视为经过改进的 mock() 方法。在 setUp() 方法内,用 Collaborator 类的构造方法所需的实际 值实例化对象数组。该数组被立刻传递给 RMock 的 intercept() 方法来帮助实例化模拟对象。方法的签名类似于 jMock CGLIB mock() 方法的签名,因为这两种方法将接纳惟一模拟对象标识符
23、作为参数。模拟对象到 Collaborator 类型的类强制转换十分有必要,因为 intercept() 方法将返回类型 Object。在测试方法本身 testRunServiceAndReturnFalse() 之内,您可以看到更多更改。模拟 Collaborator 对象的 executeJob() 方法将被调用。在此阶段,模拟对象处于记录状态 即简单地定义对象将一直期望的方法调用,因此,模拟将相应地记录期望。下一行是对模拟对象的通知,用于确保当它遇到 executeJob() 方法时,它应当返回字符串 failure。因此,使用 RMock,您只需通过调用方法而不调用模拟对象(并传递它可
24、能需要的任何参数)来描述期望,然后修改该期望以相应地调整任何返回类型。最后,调用 RMock 方法 startVerification() 把模拟 Collaborator 对象转为就绪状态。模拟对象现已准备好在 ServiceClass 类中作为实际对象使用。该方法非常重要并且必须调用它才能避免测试初始化失败。测试更改再次重新运行 ServiceClassTest 以达到最终的肯定结果:在模拟对象实例化期间提供的参数造成了所有差别。图 6 显示 JUnit 测试已经通过。图 6. 使用 RMock 的场景 3 测试成功assertFalse(result) 代码行表示与场景 1 相同的测试期
25、望,而 RMock 像 jMock 以前那样维持测试成功。在许多方面,这都十分重要,但是这里更重要的特点可能是实践了修正失败测试的灵活 原则而不更改测试期望。惟一的差别是使用了另一个框架。在下一个场景中,您将在一种特殊情况下使用 jMock 和 RMock。没有一个框架能够仅凭自身就实现正确结果,除非在测试内形成某种联合。回页首场景 4:jMock 和 RMock 之间的特定协作如前所述,我希望检验两个框架必须协同工作才能实现某个结果的情况。否则,构建良好的测试每次都将失败。在某些情况下,使用 jMock 还是 RMock 并不重要,例如,当需要模拟的接口或类存在于已经签名的 JAR 中时。此
26、类情况十分少见,但是当测试针对安全专有的产品(通常是这样或那样的一类现有软件)中的应用程序编程接口 (API) 编写代码时可能会出现此情况。清单 10 显示了两个框架完成测试用例的示例。清单 10. 场景 4 的测试示例点击查看代码清单在 setUp() 方法内,根据为扩展 jMock-CGLIB MockObjectTestCase 对象而创建的私有内部类实例化了新 testcase。使用任何 jMock 功能时,这个小解决方法对于确保整个测试类为 RMock TestCase 对象十分有必要。例如,您将设定类似 testCase.once() 而不是类似 once() 的 jMock 期望,因为 Tes
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1