EasyMock 使用方法与原理剖析
https://www.ibm.com/developerworks/cn/opensource/os-cn-easymock/
?
Mock 方法是单元测试中常见的一种技术,它的主要作用是模拟一些在应用中不容易构造或者比较复杂的对象,从而把测试与测试边界以外的对象隔离开。
编写自定义的 Mock 对象需要额外的编码工作,同时也可能引入错误。EasyMock 提供了根据指定接口动态构建 Mock 对象的方法,避免了手工编写 Mock 对象。本文将向您展示如何使用 EasyMock 进行单元测试,并对 EasyMock 的原理进行分析。
设定预期异常抛出
对象行为的预期输出除了可能是返回值外,还有可能是抛出异常。IExpectationSetters
提供了设定预期抛出异常的方法:
下面是示例代码中的一个接口 SalesOrder
,它的实现类 SalesOrderImpl
的主要功能是从数据库中读取一个 Sales Order 的 Region 和 Total Price,并根据读取的数据计算该 Sales Order 的 Price Level(完整的实现代码都可以在 src.zip 中找到):
清单2:SalesOrder 接口
?
在这个示例中,我们首先创建了 ResultSet
的 Mock 对象 moResultSet,并记录该 Mock 对象的预期行为。之后我们调用了 control.replay()
,将 Mock 对象的状态置为 Replay 状态。在实际的测试阶段,Sales Order 对象的 loadDataFromDB
方法调用了 mockResultSet 对象的 getString
和 getDouble
方法读取 mockResultSet 中的数据。Sales Order 对象根据读取的数据计算出 Price Level,并和预期输出进行比较。
EasyMock 类提供的 createNiceMock()
方法创建。类似的,你也可以用 IMocksControl
实例来创建一个 Nice Mock 对象。
和开发人员联系最紧密的是 EasyMock
类,这个类提供了 createMock、replay、verify
等方法以及所有预定义的参数匹配器。
我们知道 Mock 对象有两种创建方式:一种是通过 EasyMock
类提供的 createMock
方法创建,另一种是通过 EasyMock
类的 createControl
方法得到一个 IMocksControl
实例,再由这个 IMocksControl
实例创建 Mock 对象。其实,无论通过哪种方法获得 Mock 对象,EasyMock 都会生成一个 IMocksControl
的实例,只不过第一种方式中的 IMocksControl
的实例对开发人员不可见而已。这个 IMocksControl
的实例,其实就是 MocksControl
类的一个对象。MocksControl
类提供了 andReturn、andThrow、times、createMock
等方法。
MocksControl
类中包含了两个重要的成员变量,分别是接口 IMocksBehavior
和 IMocksControlState
的实例。其中,IMocksBehavior
的实现类 MocksBehavior
是 EasyMock 的核心类,它保存着一个 ExpectedInvocationAndResult
对象的一个列表,而 ExpectedInvocationAndResult
对象中包含着 Mock 对象方法调用和预期结果的映射。MocksBehavior
类提供了 addExpected
和 addActual
方法用于添加预期行为和实际调用。
MocksControl
类中包含的另一个成员变量是 IMocksControlState
实例。IMocksControlState
拥有两个不同的实现类:RecordState
和 ReplayState
。顾名思义,RecordState
是 Mock 对象在 Record 状态时的支持类,它提供了 invoke
方法在 Record 状态下的实现。此外,它还提供了 andReturn、andThrow、times
等方法的实现。ReplayState
是 Mock 对象在 Replay 状态下的支持类,它提供了 invoke
方法在 Replay 状态下的实现。在 ReplayState 中,andReturn、andThrow、times
等方法的实现都是抛出IllegalStateException,因为在 Replay 阶段,开发人员不应该再调用这些方法。
当我们调用 MocksControl
的 createMock
方法时,该方法首先会生成一个 JavaProxyFactory
类的对象。JavaProxyFactory
是接口 IProxyFactory
的实现类,它的主要功能就是通过 java.lang.reflect.Proxy
对指定的接口创建动态代理实例,也就是开发人员在外部看到的 Mock 对象。
在创建动态代理的同时,应当提供 InvocationHandler
的实现类。MockInvocationHandler
实现了这个接口,它的 invoke
方法主要的功能是根据 Mock 对象状态的不同而分别调用 RecordState
的 invoke
实现或是 ReplayState
的 invoke
实现。
当 EasyMock
类的 createMock
方法被调用时,它首先创建一个 MocksControl
对象,并调用该对象的 createMock
方法创建一个 JavaProxyFactory
对象和一个 MockInvocationHandler
对象。JavaProxyFactory
对象将 MockInvocationHandler
对象作为参数,通过 java.lang.reflect.Proxy
类的 newProxyInstance
静态方法创建一个动态代理。
当 MockInvocationHandler
的 invoke
方法被调用时,它首先通过 reportLastControl
静态方法将 Mock 对象对应的 MocksControl
对象报告给 LastControl
类,LastControl
类将该对象保存在一个 ThreadLocal 变量中。接着,MockInvocationHandler
将创建一个 Invocation 对象,这个对象将保存预期调用的 Mock 对象、方法和预期参数。
在记录 Mock 对象预期行为时,Mock 对象的状态是 Record 状态,因此 RecordState
对象的 invoke
方法将被调用。这个方法首先调用 LastControl
的 pullMatchers
方法获取参数匹配器。如果您还记得自定义参数匹配器的过程,应该能想起参数匹配器被调用时会将实现类的实例报告给 EasyMock,而这个实例最终保存在 LastControl
中。如果没有指定参数匹配器,默认的匹配器将会返回给 RecordState
。
根据 Invocation
对象和参数匹配器,RecordState
将创建一个 ExpectedInvocation
对象并保存下来。
在对预期方法进行调用之后,我们可以对该方法的预期输出进行设定。我们以
在预期方法被调用时,Mock 对象对应的 MocksControl
对象引用已经记录在 LastControl
中,expectLastCall
方法通过调用 LastControl
的 lastControl
方法可以获得这个引用。MocksControl
对象的 andReturn
方法在 Mock 对象 Record 状态下会调用 RecordState
的 andReturn
方法,将设定的预期输出以 Result
对象的形式记录下来,保存在 RecordState
的 lastResult 变量中。
当 MocksControl
的 times
方法被调用时,它会检查 RecordState
的 lastResult 变量是否为空。如果不为空,则将 lastResult 和预期方法被调用时创建的 ExpectedInvocation
对象一起,作为参数传递给 MocksBehavior
的 addExpected
方法。MocksBehavior
的 addExpected
方法将这些信息保存在数据列表中。
在 Replay 状态下,MockInvocationHandler
会调用 ReplayState
的 invoke
方法。该方法会把 Mock 对象通过 MocksBehavior
的 addActual
方法添加到实际调用列表中,该列表在 verify
方法被调用时将被用到。同时,addActual
方法会根据实际方法调用与预期方法调用进行匹配,返回对应的 Result
对象。调用 Result
对象的 answer
方法就可以获取该方法调用的输出。
描述名字大小下载方法本文用到的示例代码src.zip176KBHTTP
关于下载方法的信息
?
参考资料
学习
如果您想要获得 EasyMock 完整的文档和 API,您可以访问 EasyMock 的主页:http://www.easymock.org/。获得产品和技术
在Source Forge上,你可以下载到最新的 EasyMock 相关代码:http://sourceforge.net/project/showfiles.php?group_id=82958。