C/C++教程

EasyMock入门使用

本文主要是介绍EasyMock入门使用,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

目录
  • EasyMock入门使用
    • 一、EasyMock安装
    • 二、EasyMock使用
      • 2.1 使用EasyMock生成Mock对象
        • 2.1.1 为接口生成Mock对象
        • 2.1.2 为类生成Mock对象
        • 2.1.3 使用IMocksControl对象管理Mock对象
      • 2.2 设定Mock对象的预期行为和输出
        • 2.2.1 设定预期返回值
          • 2.2.1.1 返回值不是void
          • 2.2.1.2 返回值为void
        • 2.2.2 设定预期异常抛出
        • 2.2.3 设定预期方法调用次数
        • 2.2.4 若返回结果在运行时才能确定
      • 2.3 将Mock对象切换到Replay状态——replay
      • 2.4 调用Mock对象方法进行单元测试
      • 2.5 对Mock对象的行为进行验证——verify
      • 2.6 Mock对象的重用——reset
    • 三、在EasyMock中使用参数匹配器
      • 3.1 EasyMock预定义的参数匹配器
      • 3.2 EasyMock自定义参数匹配器
    • 四、特殊的Mock对象类型
      • 4.1 Strick Mock对象
      • 4.2 Nice Mock对象
    • 五、EasyMock 的工作原理
    • 六、参考文献

EasyMock入门使用

一、EasyMock安装

  1. EasyMock是一套通过简单的方法对于指定的接口或类生成Mock对象的类库,它能利用对接口或类的模拟来辅助单元测试。

  2. 要使用EasyMock辅助单元测试,添加easymock的jar包即可。

    image-20200402190315174

二、EasyMock使用

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

  • 使用EasyMock生成Mock对象
  • 设定Mock对象的预期行为和输出
  • 将Mock对象切换到Replay状态
  • 调用Mock对象方法进行单元测试
  • 对Mock对象的行为进行验证

2.1 使用EasyMock生成Mock对象

根据指定的接口或类,EasyMock能动态地创建Mock对象。

2.1.1 为接口生成Mock对象

Eg:以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 对象来解决这个问题。

static import org.easymock.Easymock; //静态方法引入
ResultSet mockResultSet = createMock(ResultSet.class);

import org.easymock.Easymock; 
ResultSet mockResultSet = Easymock.createMock(ResultSet.class);

2.1.2 为类生成Mock对象

EasyMock默认只支持为接口生成Mock对象,若要为类生成Mock对象,需下载扩展包 EasyMock Class Extension,在对具体类进行模拟时,只需把 org.easymock.EasyMock 替换为 org.easymock.classextension.Easymock

2.1.3 使用IMocksControl对象管理Mock对象

若在相对复杂的测试用例中使用多个Mock对象,可使用EasyMock提供的生成和管理Mock对象的机制。

EasyMock类的createControl方法能创建一个接口IMocksControl的对象,此对象能创建并管理多个Mock对象。

Eg:

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);

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

  1. 在一个完整测试中,一个Mock对象会经历两个状态:Record状态和Replay状态。

    Mock对象一经创建,状态为Record,Record状态下用户可以设定Mock对象的预期行为和输出,这些对象行为会被录制下来,保存在Mock对象中。

  2. 添加Mock对象行为的过程分为三步:

    • 对Mock对象的特定方法作出调用
    • 通过org.easymock.EasyMock提供的静态方法expectLastCall获取上一次方法调用所对应的IExpectionSetters实例
    • 用过IExpectionSetters实例设定Mock对象的预期输出(有两种类型)
      • 产生返回值
      • 抛出异常

    (Mock对象的行为可简单理解为Mock对象的调用和方法调用所产生的输出。)

2.2.1 设定预期返回值

设定返回值对应接口IExpectionSettersandReturn方法。

IExpectationSetters<T> andReturn(T value);
2.2.1.1 返回值不是void
  1. Eg:仍用 ResultSet 接口的 Mock 对象为例,若希望方法 mockResultSet.getString(1) 的返回值为 "My return value",则:

    mockResultSet.getString(1);
    EasyMock.expectLastCall().andReturn("My return value");
    

    或:

    EasyMock.expect(mockResultSet.getString(1)).andReturn("My return value");
    
  2. 若希望 某方法的调用总是返回一个相同的值 ,为避免每次调用都为Mock对象的行为进行一次设定,可用默认返回值的方法——andSubReturn方法。

    	void andStubReturn(Object value);
    

    Eg:假设我们创建了 StatementResultSet 接口的 Mock 对象 mockStatement 和 mockResultSet,在测试过程中,我们希望 mockStatement 对象的 executeQuery 方法总是返回 mockResultSet,则:

    mockStatement.executeQuery("SELECT * FROM sales_order_table");
    EasyMock.expectLastCall().andStubReturn(mockResultSet);
    

    或:

    EasyMock.expect(mockStatement.executeQuery("SELECT * FROM sales_order_table")).andStubReturn(mockResultSet);
    
2.2.1.2 返回值为void

若方法的返回值类型为void,则对于此类方法,我们无需设定返回值,只需设置调用次数就可以。(也可以不设置)

How设定调用次数,详看2.2.3。

Eg:以 ResultSet 接口的 close 方法为例,假设在测试过程中,该方法被调用3至5次,则:

mockResultSet.close();
EasyMock.expectLastCall().times(3,5);// 最新版本的EasyMock可以忽略此句

或:

EasyMock.expect(mockResult.close()).times(3, 5);

2.2.2 设定预期异常抛出

设定预期抛出异常对应接口IExpectionSettersandThrow方法。

IExpectationSetters<T> andThrow(Throwable throwable);

类似的,设定抛出默认异常对应接口IExpectionSettersandStubThrow方法。

void andStubThrow(Throwable throwable);

Eg:

EasyMock.expectLastCall().andThrow(
                new MyException(new RuntimeException())).anyTimes();

2.2.3 设定预期方法调用次数

  1. 设定确定的调用次数:通过接口IExpectionSetterstimes方法。
IExpectationSetters<T>times(int count);

Eg:我们希望 mockResultSet 的 getString 方法在测试过程中被调用3次,期间的返回值都是 "My return value",则:

mockResultSet.getString(1);
expectLastCall().andReturn("My return value").times(3);

或:

EasyMock.expect(mockResultSet.getString(1)).andReturn("My return value").times(3);
  1. 设定非准确调用次数:
    • times(int minTimes, int maxTimes):该方法最少被调用 minTimes 次,最多被调用 maxTimes 次。
    • atLeastOnce():该方法至少被调用一次。
    • anyTimes():该方法可以被调用任意次。

2.2.4 若返回结果在运行时才能确定

很可能某个方法期望的返回结果不是固定的,例如根据传入参数不同而不同;这时需要使用andAnswer()方法:

EasyMock.expect(mockService.execute(EasyMock.anyInt())).andAnswer(new IAnswer<Integer>() {
    public Integer answer() throws Throwable {
        Integer count = (Integer) EasyMock.getCurrentArguments()[0];
        return count * 2;
    }
});

{% note warning %}

​ 注意:通过EasyMock.getCurrentArguments()可以获取传入参数!

{% endnote %}

2.3 将Mock对象切换到Replay状态——replay

  1. 在生成Mock对象和设定Mock对象行为的两个阶段,Mock对象的状态均为Record,此阶段Mock对象会记录用户对预期行为和输出的设定。

  2. 在使用Mock对象隐形实际的测试前,需将Mock对象的状态切换为Replay,此阶段Mock对象能根据设定对特定的方法调用作出预期的响应。

  3. 将对象切换到Replay状态有两种方法:

    • 若Mock对象是通过createMock方法生成

      EasyMock.replay(mockResultSet);
      
    • 若Mock对象通过createControl方法生成的接口对象control的createMock方法生成

      control.replay();
      

      此语句可将通过coltrol的createMock方法生成的所有Mock对象均切换为Replay状态。

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

此部分放到JUnit+EasyMock实例中。

2.5 对Mock对象的行为进行验证——verify

在利用Mock对象进行实际的测试过程后,还需对Mock对象的方法调用的次数进行验证。

  • 若Mock对象是通过createMock方法生成

    EasyMock.verify(mockResultSet);
    
  • 若Mock对象通过createControl方法生成的接口对象control的createMock方法生成

    control.verify();
    

    同理,此语句可验证通过coltrol的createMock方法生成的所有Mock对象方法的调用次数。

2.6 Mock对象的重用——reset

为避免生成过多的Mock对象,EasyMock允许对原有的Mock对象进行重用。

可使用reset方法对Mock对象重新初始化。重新初始化后,Mock对象被置为Record状态。

  • 若Mock对象是通过createMock方法生成

    EasyMock.reset(mockResultSet);
    
  • 若Mock对象通过createControl方法生成的接口对象control的createMock方法生成

    control.reset();
    

    同理,此语句可验证通过coltrol的createMock方法生成的所有Mock对象重新初始化。


三、在EasyMock中使用参数匹配器

使用Mock对象进行实际的测试过程中,EasyMock会根据方法名和参数来匹配一个预期方法的调用。

此时,EasyMock对参数的匹配默认使用equals()方法进行比较,这可能会引起一些问题,因此EasyMock提供了一些参数匹配方式。

{% note info %}

如2.2.1.1中创建的mockStatement对象:

mockStatement.executeQuery("SELECT * FROM sales_order_table");
EasyMock.expectLastCall().andStubReturn(mockResultSet);

实际调用中,可能会遇到SQL语句中某些关键词大小写问题(因为SQL语句不区分大小写)如将SELECT写成select,此时EasyMock采用的默认匹配器equals方法将认为参数不匹配,则Mock对象的预期方法不会被调用。

{% endnote %}

3.1 EasyMock预定义的参数匹配器

  1. anyObject():任意输入值都与预期值匹配;
  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. startsWith(String prefix), contains(String substring), endsWith(String suffix):当输入值以预期值开头、包含预期值、以预期值结尾时匹配,适用于String类型;
  8. matches(String regex):当输入值与正则表达式匹配时匹配,适用于String类型。

例如:若我对mockStatement具体执行的语句并不关注,希望所有输入的字符串都能够匹配这一方法的调用

EasyMock.expect(mockStatement.executeQuery(anyObject())).andStubReturn(mockResultSet);

详细参阅 easymock教程-参数匹配

3.2 EasyMock自定义参数匹配器

  1. 预定义的参数匹配器无法满足一些复杂情况,此时需自己定义参数匹配器。

    如:在3.1中我们希望有一个匹配器对SQL中关键词的大小写不敏感,使用anyObject其实并不好,此时我们可以自定义参数匹配器SQLEquals。

  2. How自定义参数匹配器:

    • 实现 org.easymock.IArgumentMatcher 接口

      • matches(Object actual) 方法应当实现输入值和预期值的匹配逻辑

      • appendTo(StringBuffer buffer) 方法可以添加当匹配失败时需要显示的信息

    • 使用静态方法包装实现接口的类

     package org.easymock.demo.matcher;
     import static org.easymock.EasyMock.reportMatcher;
     import org.easymock.IArgumentMatcher;
     //实现IArgumentMatcher接口
     public class SQLEquals implements IArgumentMatcher { 
        private String expectedSQL = null;
        public SQLEquals(String expectedSQL) {
            this.expectedSQL = expectedSQL;
        }
        //当匹配失败时需要显示的信息
        public void appendTo(StringBuffer buffer) {
            buffer.append("SQLEquals(\"" + expectedSQL + "\")");
        }
        //输入值和预期值的匹配逻辑
        public boolean matches(Object actualSQL) {
            if (actualSQL == null && expectedSQL == null) return true;
            else if (actualSQL instanceof String) return expectedSQL.equalsIgnoreCase((String) actualSQL);
            else return false;
        }
        //自定义参数匹配器SQLEquals静态方法	
        public static String sqlEquals(String in) { 
            reportMatcher(new SQLEquals(in));
            return in;
        }
     }
    

    使用自定义的sqlEquals匹配器:

    EasyMock.expect(mockStatement.executeQuery(sqlEquals("SELECT * FROM sales_order_table"))).andStubReturn(mockResultSet);
    

四、特殊的Mock对象类型

上述创建的Mock对象都属于EasyMock默认的Mock对象类型,它对预期方法的调用顺序不敏感,对非预期的方法调用抛出AssertionError。

除此默认类型,EasyMock还提供一些特殊的Mock类型用于支持不同的需求。

4.1 Strick Mock对象

Stick Mock对象——对方法调用的先后顺序敏感,创建方法如下:

  1. 使用 EasyMock.createStrickMock() 来创建:

    ResultSet strickMockResultSet = EasyMock.createStrickMock(ResultSet.class);
    
  2. 类似于createMock,同样可用IMocksControl实例来创建一个Stick Mock对象:

    IMocksControl control = EasyMock.createStrictControl();
    ResultSet strickMockResultSet = control.createMock(ResultSet.class);
    

4.2 Nice Mock对象

Nice Mock对象——默认返回0,null或false等“无效值”,创建方法如下:

  1. 使用 EasyMock.createNiceMock() 来创建:

    ResultSet strickMockResultSet = EasyMock.createNiceMock(ResultSet.class);
    
  2. 类似于createMock,同样可用IMocksControl实例来创建一个Nice Mock对象:

    IMocksControl control = EasyMock.createNiceControl();
    ResultSet strickMockResultSet = control.createMock(ResultSet.class);
    

五、EasyMock 的工作原理

参阅 EasyMock使用方法与原理剖析


六、参考文献

EasyMock使用方法与原理剖析

easymock教程-参数匹配

【JUnit】EasyMock用法总结

这篇关于EasyMock入门使用的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!