2023拉

EasyMock 使用方法与原理剖析

上一篇 / 下一篇  2014-10-23 11:50:35 / 个人分类:Java学习

   

EasyMock 使用方法与原理剖析

EasyMock 是一套通过简单的方法对于指定的接口或类生成 Mock 对象的类库,它能利用对接口或类的模拟来辅助单元测试。本文将对 EasyMock 的功能和原理进行介绍,并通过示例来说明如何使用 EasyMock 进行单元测试。 

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

编写自定义的 Mock 对象需要额外的编码工作,同时也可能引入错误。EasyMock 提供了根据指定接口动态构建 Mock 对象的方法,避免了手工编写 Mock 对象。本文将向您展示如何使用 EasyMock 进行单元测试,并对 EasyMock 的原理进行分析。

1Mock 对象与 EasyMock 简介

单元测试与 Mock 方法

单元测试是对应用中的某一个模块的功能进行验证。在单元测试中,我们常遇到的问题是应用中其它的协同模块尚未开发完成,或者被测试模块需要和一些不容易构造、比较复杂的对象进行交互。另外,由于不能肯定其它模块的正确性,我们也无法确定测试中发现的问题是由哪个模块引起的。

Mock 对象能够模拟其它协同模块的行为,被测试模块通过与 Mock 对象协作,可以获得一个孤立的测试环境。此外,使用 Mock 对象还可以模拟在应用中不容易构造(如 HttpServletRequest 必须在 Servlet 容器中才能构造出来)和比较复杂的对象(如 JDBC 中的 ResultSet 对象),从而使测试顺利进行。

EasyMock 简介

手动的构造 Mock 对象会给开发人员带来额外的编码量,而且这些为创建 Mock 对象而编写的代码很有可能引入错误。目前,有许多开源项目对动态构建 Mock 对象提供了支持,这些项目能够根据现有的接口或类动态生成,这样不仅能避免额外的编码工作,同时也降低了引入错误的可能。

EasyMock 是一套用于通过简单的方法对于给定的接口生成 Mock 对象的类库。它提供对接口的模拟,能够通过录制、回放、检查三步来完成大体的测试过程,可以验证方法的调用种类、次数、顺序,可以令 Mock 对象返回指定的值或抛出指定异常。通过 EasyMock,我们可以方便的构造 Mock 对象从而使单元测试顺利进行。

安装 EasyMock

EasyMock 是采用 MIT license 的一个开源项目,您可以在 Sourceforge 上下载到相关的 zip 文件。目前您可以下载的 EasyMock 最新版本是2.3,它需要运行在 Java 5.0 平台上。如果您的应用运行在 Java 1.3 或 1.4 平台上,您可以选择 EasyMock1.2。在解压缩 zip 包后,您可以找到 easymock.jar 这个文件。如果您使用 Eclipse 作为 IDE,把 easymock.jar 添加到项目的 Libraries 里就可以使用了(如下图所示)。此外,由于我们的测试用例运行在 JUnit 环境中,因此您还需要 JUnit.jar(版本3.8.1以上)。


1Eclipse 项目中的 Libraries


回页首

 

 

2.使用 EasyMock 进行单元测试

通过 EasyMock,我们可以为指定的接口动态的创建 Mock 对象,并利用 Mock 对象来模拟协同模块或是领域对象,从而使单元测试顺利进行。这个过程大致可以划分为以下几个步骤:

· 使用 EasyMock 生成 Mock 对象; 

· 设定 Mock 对象的预期行为和输出; 

· 将 Mock 对象切换到 Replay 状态; 

· 调用 Mock 对象方法进行单元测试; 

· 对 Mock 对象的行为进行验证。 

接下来,我们将对以上的几个步骤逐一进行说明。除了以上的基本步骤外,EasyMock 还对特殊的 Mock 对象类型、特定的参数匹配方式等功能提供了支持,我们将在之后的章节中进行说明。

使用 EasyMock 生成 Mock 对象

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


清单1ResultSet 接口

                

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 将其引入(注:static import 是 java 5.0 所提供的新特性)。

如果需要在相对复杂的测试用例中使用多个 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 方法能创建一个接口 IMocksControl 的对象,该对象能创建并管理多个 Mock 对象。如果需要在测试中使用多个 Mock 对象,我们推荐您使用这一机制,因为它在多个 Mock 对象的管理上提供了相对便捷的方法。

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

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

在一个完整的测试过程中,一个 Mock 对象将会经历两个状态:Record 状态和 Replay 状态。Mock 对象一经创建,它的状态就被置为 Record。在 Record 状态,用户可以设定 Mock 对象的预期行为和输出,这些对象行为被录制下来,保存在 Mock 对象中。

添加 Mock 对象行为的过程通常可以分为以下3步: 

· 对 Mock 对象的特定方法作出调用; 

· 通过 org.easymock.EasyMock 提供的静态方法 expectLastCall 获取上一次方法调用所对应的 IExpectationSetters 实例; 

· 通过 IExpectationSetters 实例设定 Mock 对象的预期输出。 

设定预期返回值 

Mock 对象的行为可以简单的理解为 Mock 对象方法的调用和方法调用所产生的输出。在 EasyMock 2.3 中,对 Mock 对象行为的添加和设置是通过接口 IExpectationSetters 来实现的。Mock 对象方法的调用可能产生两种类型的输出:(1)产生返回值;(2)抛出异常。接口 IExpectationSetters 提供了多种设定预期输出的方法,其中和设定返回值相对应的是 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() 方法。因此,如果我们以 "select * from sales_order_table" 作为参数,预期方法将不会被调用。如果您希望上例中的 SQL 语句能不区分大小写,可以用特殊的参数匹配器来解决这个问题,我们将在 "在 EasyMock 中使用参数匹配器一章对此进行说明。

设定预期异常抛出 

对象行为的预期输出除了可能是返回值外,还有可能是抛出异常。IExpectationSetters 提供了设定预期抛出异常的方法: 

IExpectationSetters<T> andThrow(Throwable throwable);

 

和设定默认返回值类似,IExpectationSetters 接口也提供了设定抛出默认异常的函数: 

void andStubThrow(Throwable throwable);

 

设定预期方法调用次数 

通过以上的函数,您可以对 Mock 对象特定行为的预期输出进行设定。除了对预期输出进行设定,IExpectationSetters 接口还允许用户对方法的调用次数作出限制。在 IExpectationSetters 所提供的这一类方法中,常用的一种是 times 方法: 

IExpectationSetters<T>times(int count);

 

该方法可以 Mock 对象方法的调用次数进行确切的设定。假设我们希望 mockResultSet 的 getString 方法在测试过程中被调用3次,期间的返回值都是 "My return 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():该方法可以被调用任意次。 

某些方法的返回值类型是 void,对于这一类方法,我们无需设定返回值,只要设置调用次数就可以了。以 ResultSet 接口的 close 方法为例,假设在测试过程中,该方法被调用35次: 

mockResultSet.close();

expectLastCall().times(3, 5);

-5 -3 -1 - +1 +3 +5

评分:0

我来说两句

日历

« 2024-03-28  
     12
3456789
10111213141516
17181920212223
24252627282930
31      

数据统计

  • 访问量: 1067559
  • 日志数: 555
  • 文件数: 10
  • 建立时间: 2011-06-21
  • 更新时间: 2021-06-24

RSS订阅

Open Toolbar