EasyMock使用方法与原理剖析

来源:互联网 发布:h5棋牌前景 知乎 编辑:程序博客网 时间:2024/06/05 15:47

Mock方法是单元测试中常见的一种技术,它的主要作用是模拟一些在应用中不容易构造或者比较复杂的对象,从而把测试与测试边界外的对象隔离开。

EasyMock提供了根据指定接口动态构建Mock对象的方法,避免了手工编写Mock对象。

Mock对象与EasyMock简介

单元测试是对应用中的某一个模块的功能进行验证。在单元测试中,我们常遇到的问题是应用中其它的协同模块尚未开发完成,或者被测试模块需要和一些不容易构造、比较复杂的对象进行交互。另外,由于不能肯定其它模块的正确性,我们也无法确定测试中发现的问题是由哪个模块引起的。
Mock对象能够模拟其它协同模块的行为,被测试模块通过与Mock对象协作,可以获得一个孤立的测试环境。此外,使用Mock对象还可以模拟在应用中不容易构造(如HttpServletRequest必须在Servlet容器中才能构造出来)和比较复杂的对象(如JDBC中的ResultSet对象),从而使测试顺利进行。


1.引入easymock.jar

2.使用easymock生成mock对象

根据指定的接口或类,EasyMock能够动态的创建Mock对象(EasyMock默认只支持为接口生成Mock对象,如果需要为类生成Mock对象,在EasyMock的主页上有扩展包,可以实现此功能),我们以ResultSet接口为例说明EasyMock的功能。java.sql.ResultSet是每一个Java开发人员都非常熟悉的接口。
public interface java.sql.ResultSet {......public abstract java.lang.String getString(int arg0) throws java.sql.SQLException;public abstract double getDouble(int arg0) throws java.sql.SQLException;......}

通常,构建一个真实的RecordSet对象需要经过一个复杂的过程:在开发过程中,开发人员通常会编写一个DBUtility类来获取数据连接Connection,并利用Connection创建一个Statement。执行一个Statement可以猎取一个或多个ResultSet对象。这样的构造过程复杂并且依赖于数据库的正确运行。数据库或是数据库交互模块出现问题,都会影响单元测试的结果。
我们可以使用EasyMock动态构建ResultSet接口的Mock对象来解决这个问题。一些简单的测试用例只需要一个Mock对象,这时,我们可以用以下方法来创建Mock对象:
ResultSet mockResultSet = createMock(ResultSet.class);

其中createMock是org.easymock.EasyMock类所提供的静态方法,你可以通过static import将其引入。
如果需要在相对复杂的测试用例中使用多个mock对象,EasyMock提供了另外一种生成和管理Mock对象的机制:
IMocksControl control = EasyMock.createControl();java.sql.Connection mockConnection = control.createMock(Connection.class);java.sql.Statement mockStatement = control.createMock(Statement.class);java.sql.ResultSet mockResultSet = control.createMock(ResultSet.class);

EasyMock类的createControl方法能够创建一个接口IMockControl的对象,该对象能创建并管理多个Mock对象。如果需要在测试中使用多个Mock对象,我们推荐使用这一机制,因为它在多个Mock对象的管理上提供了相对便捷的方法。

如果要模拟的是一个具体的类而非接口,那么需要下载扩展包EasyMock Class Extension。在具体类进行模拟时,只需要用org.easymock.classextension.EasyMock类中的静态方法代替org.easymock.EasyMock类中的静态方法即可。

3.设定Mock对象的预期行和输出

在一个完整的测试过程中,一个Mock对象将会经历两个状态:Record状态和Replay状态。Mock对象一经创建,它的状态就被置为Record。在Record状态,用户可以设定Mock对象的预期行为和输出,这些对象行为被录制下来,保存在Mock对象中。
设定预期返回值
Mock对象的行为可以简单的理解为Mock对象方法的调用和方法调用所产生的输出。对Mock对象的行为的添加和设置是通过接口IExpectationSetters来实现的。Mock对象方法的调用可能产生两种类型的输出:(1)产生返回值(2)抛出异常。
接口IExpectationSetter提供了多种设定预期输出的方法,和设定返回值相对应的是andReturn方法:
IExpectationSetters<T> andReturn(T value);


仍然用ResultSet接口的Mock对象为例,如果希望方法mockResult.getString(1)的返回值为“My return value”,那么你可以使用以下的语句:
mockResultSet.getString(1);expectLastCall().andReturn("My return value");

以上的语句表示mockResultSet的getString方法被调用一次,这次调用的返回值是“My return value”。有时,我们希望某个方法的调用总是返回一个相同的值,为了避免每次调用都为Mock对象的行为进行一次设定,我们可以用设置默认返回值的方法:
void andStubReturn(Object value);


假设我们创建了Statement和ResultSet接口的Mock对象mockStatement和mockResultSet,在测试过程中,我们希望mockStatement对象的executeQuery方法总是返回mockResultSet,我们可以使用如下的语句
mockStatement.executeQuery("SELECT * FROM sales_order_table");expectLastCall().andStubReturn(mockResultSet);

EasyMock在对参数值进行匹配时,默认采用Object.equals()方法。可以通过参数匹配顺修改。

设定预期异常抛出

IExpectationSetter提供了设定预期抛出异常的方法:
IExpectationSetters<T> andThrow(Throwable throwable);
和设定默认返回值类似,IExpectationSetters接口也提供了设定抛出默认异常函数
void andStubThrow(Throwable throwable);

设定预期方法调用次数

IExpectationSetters接口还允许用户对方法的调用次数作出限制。在IExpectationSetters所提供的这一类方法中,常用的一种是times方法:
IExpectationSetters<T>times(int count);
假设我们希望mockResultSet的getString方法在测试过程中被调用3次,期间的返回值都是“My result value”,我们可以用如下语句:
mockResultSet.getString(1);expectLastCall().andReturn("My return value").times(3);


注意到andReturn和andThrow方法的返回值依然是一个IExpectationSetters实例,因此我们可以在此基础上继续调用times方法。
IExpectationsetters还提供了另外几种设定非准确的调用次数的方法:
times(int minTimes,int maxTimes):该方法最少被调用minTimes次,最多被调用maxTimes次。
atLeastOnce():该方法至少被调用一次。
anyTimes():该方法可以被调用任意次。
为了简化书写,EasyMock还提供了另一种设定Mock对象的行为语句模式。
expect(mockResult.close()).times(3, 5);


将Mock对象切换到Replay状态

在使用Mock对象进行实际的测试前,我们需要将Mock对象的状态切换为Replay。在Replay状态,Mock对象能够根据设定对特定方法调用作出预期的响应。将Mock对象切换成Replay状态有两种方式,需要根据Mock对象的生成方式进行选择。如查Mock对象是通过org.easymock.EasyMock类提供的静态方法createMock生成的

replay(mockResultSet);

如果Mock对象是通过IMocksControl接口提供的createMock方法生成的
control.replay();

对Mock对象的行为进行验证

在利用Mock对象进行实际的测试过程之后,我们还要对Mock对象的方法调用的次数进行验证。和replay方法类似,根据Mock对象的生成方式来选择不同的验证方式。

verify(mockResultSet);

control.verify();

Mock对象的重用

要对Mock对象生初始化,可以采用reset方法,EasyMock也提供了两种reset方法,在重新初始化之后,Mock对象的状态将被置为Record状态。


参数匹配器

EasyMock预定义的参数匹配器

(1)anyObject方法表示任意输入值都与预期值相匹配,除了anyObject以外,EasyMock还提供了多个预先定义的参数匹配器。
(2)aryEq(X value):通过Arrays.equals()进行匹配,适用于数组对象。
(3)isNull():当输入值为Null时匹配。
(4)notNull():当输入值不为Null时匹配。
(5)same(X value):当输入值和预期值是同一个对象时匹配
(6)lt(X value),leq(X value),geq(X value),gt(X value):当输入值小于、小于等于、大于等于、大于预期值时匹配,适用于数值类型
(7)startsWit(String prefix),contains(String substring),endsWith(String suffix):当输入值以预期值开头、包含,结尾时匹配,适用于String类型。
(8)matches(String regex):当输入值与正则表达式匹配时匹配,适用于String类型。

自定义参数匹配器

要定义新的参数匹配器,需要实现org.easymock.IArgumentMatcher接口。其中matches(Object actual)方法应当实现输入值和预期值的匹配逻辑,而在appendTo(StringBuffer buffer)方法中,你可以添加当匹配失败时需要显示的信息。

特殊的Mock对象类型

Strick Mock对象

如果要创建方法调用的先后次序敏感的Mock对象(Strick Mock),应该使用EasyMock.createStrickMock()来创建。
ResultSet strickMockResultSet = createStrickMock(ResultSet.class);
IMocksControl control = EasyMock.createStrictControl();ResultSet strickMockResultSet = control.createMock(ResultSet.class);

Nice Mock对象

使用createMock()创建的Mock对象对非预期的方法调用默认的行为是抛出AssertionError,如果需要一个默认返回0,null或false等无效值的Nice Mock对象。

EasyMock的工作原理

其实,EasyMock后台处理的主要原理是利用java.lang.reflect.Proxy为指定的接口创建一个动态代理,这个动态代理,就是我们在编码中使用的Mock对象。



原文:https://www.ibm.com/developerworks/cn/opensource/os-cn-easymock/

原创粉丝点击