EasyMock 简介

来源:https://www.ibm.com/developerworks/cn/opensource/os-cn-easymock/

使用注意

a、静态方法( static 修饰)无法模拟。


1、使用 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 开发人员都非常熟悉的接口:
清单1:ResultSet 接口

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 方法为例,假设在测试过程中,该方法被调用3至5次:

mockResultSet.close();
expectLastCall().times(3, 5);


为了简化书写,EasyMock 还提供了另一种设定 Mock 对象行为的语句模式。对于上例,您还可以将它写成:
expect(mockResult.close()).times(3, 5);

将 Mock 对象切换到 Replay 状态
在生成 Mock 对象和设定 Mock 对象行为两个阶段,Mock 对象的状态都是 Record 。在这个阶段,Mock 对象会记录用户对预期行为和输出的设定。
在使用 Mock 对象进行实际的测试前,我们需要将 Mock 对象的状态切换为 Replay。在 Replay 状态,Mock 对象能够根据设定对特定的方法调用作出预期的响应。将 Mock 对象切换成 Replay 状态有两种方式,您需要根据 Mock 对象的生成方式进行选择。如果 Mock 对象是通过 org.easymock.EasyMock 类提供的静态方法 createMock 生成的(第1节中介绍的第一种 Mock 对象生成方法),那么 EasyMock 类提供了相应的 replay 方法用于将 Mock 对象切换为 Replay 状态:
replay(mockResultSet);
如果 Mock 对象是通过 IMocksControl 接口提供的 createMock 方法生成的(第1节中介绍的第二种Mock对象生成方法),那么您依旧可以通过 IMocksControl 接口对它所创建的所有 Mock 对象进行切换:
control.replay();


调用 Mock 对象方法进行单元测试
下面是示例代码中的一个接口 SalesOrder,它的实现类 SalesOrderImpl 的主要功能是从数据库中读取一个 Sales Order 的 Region 和 Total Price,并根据读取的数据计算该 Sales Order 的 Price Level(完整的实现代码都可以在 src.zip 中找到):
清单2:SalesOrder 接口
public interface SalesOrder
{……public void loadDataFromDB(ResultSet resultSet) throws SQLException;	public String getPriceLevel();
}
其实现类 SalesOrderImpl 中对 loadDataFromDB 的实现如下:
清单3:SalesOrderImpl 实现

public class SalesOrderImpl implements SalesOrder
{......public void loadDataFromDB(ResultSet resultSet) throws SQLException{orderNumber = resultSet.getString(1);region = resultSet.getString(2);totalPrice = resultSet.getDouble(3);}......
}
方法 loadDataFromDB 读取了 ResultSet 对象包含的数据。当我们将之前定义的 Mock 对象调整为 Replay 状态,并将该对象作为参数传入,那么 Mock 对象的方法将会返回预先定义的预期返回值。完整的 TestCase 如下:

清单4:完整的TestCase

public class SalesOrderTestCase extends TestCase {public void testSalesOrder() {IMocksControl control = EasyMock.createControl();......ResultSet mockResultSet = control.createMock(ResultSet.class);try {......mockResultSet.next();expectLastCall().andReturn(true).times(3);expectLastCall().andReturn(false).times(1);mockResultSet.getString(1);expectLastCall().andReturn("DEMO_ORDER_001").times(1);expectLastCall().andReturn("DEMO_ORDER_002").times(1);expectLastCall().andReturn("DEMO_ORDER_003").times(1);mockResultSet.getString(2);expectLastCall().andReturn("Asia Pacific").times(1);expectLastCall().andReturn("Europe").times(1);expectLastCall().andReturn("America").times(1);mockResultSet.getDouble(3);expectLastCall().andReturn(350.0).times(1);expectLastCall().andReturn(1350.0).times(1);expectLastCall().andReturn(5350.0).times(1);control.replay();......int i = 0;String[] priceLevels = { "Level_A", "Level_C", "Level_E" };while (mockResultSet.next()) {SalesOrder order = new SalesOrderImpl();order.loadDataFromDB(mockResultSet);assertEquals(order.getPriceLevel(), priceLevels[i]);i++;}control.verify();} catch (Exception e) {e.printStackTrace();}}
}
在这个示例中,我们首先创建了 ResultSet 的 Mock 对象 moResultSet,并记录该 Mock 对象的预期行为。之后我们调用了 control.replay(),将 Mock 对象的状态置为 Replay 状态。在实际的测试阶段,Sales Order 对象的 loadDataFromDB 方法调用了 mockResultSet 对象的 getString 和 getDouble 方法读取 mockResultSet 中的数据。Sales Order 对象根据读取的数据计算出 Price Level,并和预期输出进行比较。

对 Mock 对象的行为进行验证

在利用 Mock 对象进行实际的测试过程之后,我们还有一件事情没有做:对 Mock 对象的方法调用的次数进行验证。

为了验证指定的方法调用真的完成了,我们需要调用 verify 方法进行验证。和 replay 方法类似,您需要根据 Mock 对象的生成方式来选用不同的验证方式。如果 Mock 对象是由 org.easymock.EasyMock 类提供的 createMock 静态方法生成的,那么我们同样采用 EasyMock 类的静态方法 verify 进行验证:

verify(mockResultSet);
如果Mock对象是有 IMocksControl 接口所提供的 createMock 方法生成的,那么采用该接口提供的 verify 方法,例如第1节中的 IMocksControl 实例 control:
control.verify();


Mock 对象的重用

为了避免生成过多的 Mock 对象,EasyMock 允许对原有 Mock 对象进行重用。要对 Mock 对象重新初始化,我们可以采用 reset 方法。和 replay 和 verify 方法类似,EasyMock 提供了两种 reset 方式:(1)如果 Mock 对象是由 org.easymock.EasyMock 类中的静态方法 createMock 生成的,那么该 Mock 对象的可以用 EasyMock 类的静态方法 reset 重新初始化;(2)如果 Mock 方法是由 IMocksControl 实例的 createMock 方法生成的,那么该 IMocksControl 实例方法 reset 的调用将会把所有该实例创建的 Mock 对象重新初始化。
在重新初始化之后,Mock 对象的状态将被置为 Record 状态。


























本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/498041.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

login控件authenticate_asp.net Login控件基本属性及事件说明

当前位置:IT大杂烩>JavaScript> asp.net Login控件基本属性及事件说明asp.net Login控件基本属性及事件说明www.someabcd.com 网友分享于&#xff1a;Jun 8, 2018 9:43:39 AM原文:asp.net Login控件基本属性及事件说明 Login系列控件是微软为了简化我们的开发过程&#…

全球半导体产业迁移 中国的机遇与挑战

来源&#xff1a;36氪 概要&#xff1a;商务部24日发布公告说&#xff0c;以附加“限制性条件”的形式批准了日月光半导体收购矽品精密股权案。 商务部24日发布公告说&#xff0c;以附加“限制性条件”的形式批准了日月光半导体收购矽品精密股权案。这个附加的“限制性条件”&…

dts同步常见问题_阿里云DTS数据同步常见问题(一)

阿里云的数据同步工具DTS确实是一件非常不错的工具&#xff0c;可以实现不同数据源之间的数据迁移、数据同步&#xff0c;只需要配置好两端的数据源就可以自动实现&#xff0c;不在需要人为的操作&#xff0c;非常的方便。但是如果不熟悉DTS的话呢&#xff0c;会遇到各种各样的…

暂时

/*** 使HTML的标签失去作用* * param input* 被操作的字符串* return String*/public static final String escapeHTMLTag(String input) {if (input null) {input "";return input;}input input.trim().replaceAll("&", "&&qu…

mysql的程序怎么升级成mysqli_如何将mysql更改为mysqli?-问答-阿里云开发者社区-阿里云...

首先要做的可能是将每个mysql_函数调用都替换为等效函数mysqli_&#xff0c;至少在您愿意使用过程式API的情况下-考虑到您已经有一些基于MySQL API的代码&#xff0c;这将是更简单的方法是一种程序性的。为了解决这个问题&#xff0c;“ MySQLi扩展功能摘要”绝对是有用的。例如…

待完成任务列表

1、将 HT 控件全部对象化。 a、抽象出 print 方法 b、使用构建器模式控制参数&#xff1a;部分是必须在创建对象时给定&#xff0c;部分是可以给定也可以使用默认值。 2、开发——小助手&#xff0c;工具 a、要对“名称定义”、“单元测试”进行&#xff0c;添加&#xff0c;编…

mysql插入实现存在更新_mysql 记录不存在时插入 记录存在则更新的实现方法

mysql 记录不存在时插入在 MySQL 中&#xff0c;插入(insert)一条记录很简单&#xff0c;但是一些特殊应用&#xff0c;在插入记录前&#xff0c;需要检查这条记录是否已经存在&#xff0c;只有当记录不存在时才执行插入操作&#xff0c;本文介绍的就是这个问题的解决方案。问题…

阿里智能对话交互实践与创新

来源&#xff1a;人工智能头条 作者 &#xff1a;孙健&#xff0c;李永彬&#xff0c;陈海青&#xff0c;邱明辉 概要&#xff1a;过去 20 多年&#xff0c;互联网及移动互联网将人类带到了一个全新的时代&#xff0c;如果用一个词来总结和概括这个时代的话&#xff0c;「连接」…

HT 相关

设置系统语言——日语测试用 解决方案 按照以下说明将系统语言环境更改为您所需的语言&#xff1a; 更改 Windows XP、Vista 和 Windows 7 的系统位置 注&#xff1a;必须以具有管理权限的用户身份登录。 单击开始 > 控制面板。 Windows 7 和 Vista&#xff1a;依次单击…

IDC Future Scape : 2018年全球物联网十大趋势性预测,5G将加速IoT发展

作者&#xff1a;Dudu 概要&#xff1a;预计到2021年前后&#xff0c;5G、物联网数据分析、物联网支出管理、区块链、物联网服务将成为市场主流。 2019年&#xff0c;IoT行业中&#xff0c;边缘基础设施将成为市场主流&#xff0c;多用于单个部门的业务板块中。 到了2020年&am…

mysql 解释 游标赋值_Mysql_游标

MySQL中的游标是一个十分重要的概念。游标提供了一种对从表中检索出的数据进行操作的灵活手段&#xff0c;就本质而言&#xff0c;游标实际上是一种能从包括多条数据记录的结果集中每次提取一条记录的机制。MySQL中的游标的语法如下&#xff1a;DECLARE cursor-name CURSOR FOR…

意见征集,世界AI智商评测量标准2018年新版讨论方案

来源&#xff1a;未来智能实验室 对于本次2018年世界AI智商评测的量表更新 &#xff0c;有两个问题希望得到您的意见&#xff1a;1.如果按上述智力因素进行增加&#xff0c;您认为他们的权重应该是多少&#xff0c;其他已有的智力因素权重应该调整为多少&#xff1b;2.您认为考…

mysql查看system函数_mysql系统信息函数

1、VERSION() 返回数据库的版本号SELECT VERSION() -- 5.0.67-community-nt2、CONNECTION_ID() 返回服务器的连接数SELECT CONNECTION_ID() -- 33、DATABASE()、SCHEMA 返回当前数据库名4、USER()、SYSTEM_USER()、SESSION_USER()、CURRENT_USER()、CURRENT_USER 返回当前用户S…

chm文件大不开

1、如果提示是&#xff1a;如果提示是Internet Explorer 不能链接到您请求的网页或者打开后“页面无法显示”。请下载chm.reg&#xff0c;执行。或在要打开的CHM文件上右键属性&#xff0c;会在底下属性中多了一个“解除锁定”&#xff0c;点击后就可以正常显示了。 chm.reg内容…

人工智能的价值地图:AI产业增强革命的模式与路径

来源&#xff1a;腾讯研究院 概要&#xff1a;人工智能所蕴含的力量让人向往又恐惧。2016年的两次人机大战第一次让公众认识到人工智能的强大力量。 “智造”并不是一个新词&#xff0c;几年前&#xff0c;我们可以看到数字技术从虚拟世界向实体世界渗透。3D打印、激光切割等一…

怎样打开mysql进程数_mysql查看最大打开进程数

今天在群里刚刚知道这个查看打开最大进程数的命令&#xff0c;下面来看一下吧~ cat /proc/27095/limits | grep Max open files 查看mysql最大打开进程数的命令 我们应该如何知道那个标黄的进程呢&#xff0c;想起来前几天刚刚学过的命令了吗&#xff0c;对的&#xff0c;就是l…

jsp 页面获取action 属性的方法

1、代码法 <%ValueStack valueStack (ValueStack)request.getAttribute("struts.valueStack");String[] books (String[])valueStack.findValue("books");for(String book : books){%><tr><td>BookName:</td><td><%boo…

那么多GAN哪个好?谷歌大脑泼来冷水:都和原版差不多

来源&#xff1a;量子位 概要&#xff1a;从2014年诞生至今&#xff0c;生成对抗网络&#xff08;GAN&#xff09;热度只增不减&#xff0c;各种各样的变体层出不穷。 从2014年诞生至今&#xff0c;生成对抗网络&#xff08;GAN&#xff09;热度只增不减&#xff0c;各种各样的…

java 中获取file的长度为0_Java核心技术梳理-IO

一、引言IO(输入/输出)&#xff0c;输入是指允许程序读取外部数据(包括来自磁盘、光盘等存储设备的数据)、用户输入数据。输出是指允许程序记录运行状态&#xff0c;将程序数据输出到磁盘、光盘等存储设备中。IO的主要内容包括输入、输出两种IO流&#xff0c;这两种流中又分为字…

Exception 和解决办法

1、 org.xml.sax.SAXParseException: The string "--" is not permitted within comments. 从错误描述来看发现是&#xff38;&#xff2d;&#xff2c;文件中的注释引起的异常&#xff0c;去掉配置文件中的中文注释或改用英文描述则可以通过后来发现是配置文件中采…