mock测试框架Mockito

不管是敏捷开发、持续交付,仍是测试驱动开发(TDD)都把单元测试做为实现的基石。随着这些先进的编程开发模式日益深刻人心,单元测试现在显得愈来愈重要了。在敏捷开发、持续交付中要求单元测试必定要快(不能访问实际的文件系统或数据库),而TDD常常会碰到协同模块还没有开发的状况,而mock技术正是解决这些问题的灵丹妙药。java

mock技术的目的和做用是模拟一些在应用中不容易构造或者比较复杂的对象,从而把测试与测试边界之外的对象隔离开。数据库

咱们能够本身编写自定义的Mock对象实现mock技术,可是编写自定义的Mock对象须要额外的编码工做,同时也可能引入错误。如今实现mock技术的优秀开源框架有不少,本文对几个典型的mock测试框架做了简明介绍,但愿对你们有所帮助。编程

1.EasyMock

EasyMock 是早期比较流行的MocK测试框架。它提供对接口的模拟,可以经过录制、回放、检查三步来完成大致的测试过程,能够验证方法的调用种类、次数、顺序,能够令 Mock 对象返回指定的值或抛出指定异常。经过 EasyMock,咱们能够方便的构造 Mock 对象从而使单元测试顺利进行。api

EasyMock 是采用 MIT license 的一个开源项目,能够在 Sourceforge 上下载到。(http://sourceforge.net/projects/easymock/files/EasyMock/)架构

若是使用maven也能够以下引入:框架

复制代码
<dependency>
  <groupId>org.easymock</groupId>
  <artifactId>easymock</artifactId>
  <version>3.1</version>
  <scope>test</scope>
</dependency>
复制代码

使用EasyMock大体能够划分为如下几个步骤:maven

①    使用 EasyMock 生成 Mock 对象;函数

②    录制 Mock 对象的预期行为和输出;工具

③    将 Mock 对象切换到 播放 状态;post

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

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

如今用一个例子来简单呈现以上的步骤,假设有一个类须要被模拟的类以下:

复制代码
public class Class1Mocked {
         public  String hello(String name){
                   System.out.println("hello "+name);
                   return "hello "+name;
         }
         public void show(){
                   System.out.println("Class1Mocked.show()");
         }
}
复制代码

 

  首先静态导入EasyMock的方法:

import static org.easymock.EasyMock.*;

 

例1.1 EasyMock第一个例子

复制代码
@Test
public void testMockMethod() {
         Class1Mocked obj = createMock(Class1Mocked.class);①

         expect(obj.hello("z3")).andReturn("hello l4");②
         replay(obj);③

         String actual = obj.hello("z3");④
         assertEquals("hello l4", actual);

         verify(obj);⑤
}
复制代码

 

在⑤验证阶段中,会严格验证mock对象是否按录制的行为如期发生(包括执行的顺序及次数)。

 

2.mockito

EasyMock以后流行的mock工具。相对EasyMock学习成本低,并且具备很是简洁的API,测试代码的可读性很高。

mockito能够在https://code.google.com/p/mockito/上下载,若是使用maven能够以下引入:

复制代码
<dependency>
  <groupId>org.mockito</groupId>
  <artifactId>mockito-all</artifactId>
  <version>1.9.5</version>
  <scope>test</scope>
</dependency>
复制代码

使用mockito大体能够划分为如下几个步骤:

①    使用 mockito 生成 Mock 对象;

②    定义(并不是录制) Mock 对象的行为和输出(expectations部分);

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

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

如今用一个例子来简单呈现以上的步骤:

  首先静态导入mockito的方法:

import static org.mockito.Mockito.*;

例2.1 mockito第一个例子

复制代码
@Test
public void testMockMethod() {
    Class1Mocked obj=mock(Class1Mocked.class);①

    when(obj.hello("z3")).thenReturn("hello l4");②

    String actual=obj.hello("z3");③
    assertEquals("hello l4",actual);
    
    verify(obj).hello("z3");④
    //verify(obj,times(1)).hello("z3"); //能够加参数验证次数
}
复制代码

能够看到与EasyMock相比,少了切换到播放状态一步。这是很天然的,原本就不是录制而谈播放呢,而在验证阶段能够经过增长参数(time(int)、atLeastOnce()、atLeast(int)、never()等)来精确验证调用次数。

而若是要验证调用顺序能够以下控制:

例2.2 验证顺序

复制代码
@Test
public void testMockMethodInOrder() {
    Class1Mocked objOther = mock(Class1Mocked.class);
    Class1Mocked objCn = mock(Class1Mocked.class);

    when(objOther.hello("z3")).thenReturn("hello l4");
    when(objCn.hello("z3")).thenReturn("hello 张三");

    String other = objOther.hello("z3");
    assertEquals("hello l4", other);
    String cn = objCn.hello("z3");
    assertEquals("hello 张三", cn);

    InOrder inOrder = inOrder(objOther, objCn); //此行并不决定顺序,下面的两行才开始验证顺序
    inOrder.verify(objOther).hello("z3");
    inOrder.verify(objCn).hello("z3");
}
复制代码

在以前的介绍的模拟操做中,咱们老是去模拟一整个类或者对象,对于没有使用 When().thenReturn()方法指定的函数,系统会返回各类类型的默认值(具体值可参考官方文档)。而局部模拟建立出来的模拟对象依然是原系统对象,虽然可使用方法When().thenReturn()来指定某些具体方法的返回值,可是没有被用此函数修改过的函数依然按照系统原始类的方式来执行,下面对非局部模拟和局部模拟分别举例来讲明:

例2.3 非局部模拟

复制代码
@Test
public void testSkipExpect() {
    Class1Mocked obj = mock(Class1Mocked.class);

    assertEquals(null, obj.hello("z3"));
    obj.show();

    verify(obj).hello("z3");
    verify(obj).show();
}
复制代码

上面的代码省略了expectations部分(即定义代码行为和输出),运行该测试能够看到hello方法默认返回null(show方法原本就是无返回值的),并且在控制台中两个方法都没有输出任何语句。

mockito的局部模拟有两种方式,一种是doCallRealMethod()方式,另外一种是spy()方式。

例2.4 局部模拟doCallRealMethod ()方式

复制代码
@Test
public void testCallRealMethod () {
    Class1Mocked obj = mock(Class1Mocked.class);

    doCallRealMethod().when(obj).hello("z3"); 
    
    assertEquals("hello z3",obj.hello("z3"));
    assertEquals(null,obj.hello("l4"));
    obj.show();

    verify(obj).hello("z3");
    verify(obj).hello("l4");
    verify(obj).show();
}
复制代码

运行这个测试会发如今执行hello("z3")时会执行原有的代码,而执行hello("l4")时则是返回默认值null且没有输出打印,执行show()一样没有输出打印。

例2.5 局部模拟spy()方式

复制代码
@Test
public void testSpy() {
    Class1Mocked obj = spy(new Class1Mocked());
    
    doNothing().when(obj).show();
    
    assertEquals("hello z3",obj.hello("z3"));
    obj.show();
    
    verify(obj).hello("z3");
    verify(obj).show();
}
复制代码

运行这个测试会发如今执行hello("z3")时会执行原有的代码,可是执行show()时在控制台中没有打印语句。

但值得注意的是在mockito的psy()方式模拟中expectations部分使用的语法不一样,执行起来存在微妙的不一样,以下:

例2.6 值得注意的“陷阱”

复制代码
@Test
public void testSpy2() {
    Class1Mocked obj = spy(new Class1Mocked());
    
    when(obj.hello("z3")).thenReturn("hello l4");
    
    assertEquals("hello l4",obj.hello("z3"));
    
    verify(obj).hello("z3");
}
复制代码

上面的代码虽然能顺利运行,但在控制台中输出了hello z3,说明实际的代码仍然执行了,只是mockito在最后替换了返回值。但下面的代码就不会执行实际的代码:

复制代码
@Test
public void testSpy3() {
    Class1Mocked obj = spy(new Class1Mocked());
    
    doReturn("hello l4").when(obj).hello("z3");
    
    assertEquals("hello l4",obj.hello("z3"));
    
    verify(obj).hello("z3");
}
复制代码

 

3.PowerMock

这个工具是在EasyMock和Mockito上扩展出来的,目的是为了解决EasyMock和Mockito不能解决的问题,好比对static, final, private方法均不能mock。其实测试架构设计良好的代码,通常并不须要这些功能,但若是是在已有项目上增长单元测试,老代码有问题且不能改时,就不得不使用这些功能了。

PowerMock 在扩展功能时彻底采用和被扩展的框架相同的 API, 熟悉 PowerMock 所支持的模拟框架的开发者会发现 PowerMock 很是容易上手。PowerMock 的目的就是在当前已经被你们所熟悉的接口上经过添加极少的方法和注释来实现额外的功能。目前PowerMock 仅扩展了 EasyMock 和 mockito,须要和EasyMock或Mockito配合一块儿使用。

PowerMock能够在https://code.google.com/p/powermock/上下载,本文以PowerMock+mockito为例,使用maven的话,添加以下依赖便可,maven会自动引入mockito的包。

复制代码
<dependency>
  <groupId>org.powermock</groupId>
  <artifactId>powermock-api-mockito</artifactId>
  <version>1.5</version>
<scope>test</scope>
</dependency>
<dependency>
  <groupId>org.powermock</groupId>
  <artifactId>powermock-module-junit4</artifactId>
  <version>1.5</version>
<scope>test</scope>
</dependency>
复制代码

如今举例来讲明PowerMock的使用,假设有一个类须要被模拟的类以下:

复制代码
public class Class2Mocked {
    public static int getDouble(int i){
        return i*2;
    }
    public String getTripleString(int i){
        return multiply3(i)+"";
    }
    private int multiply3(int i){
        return i*3;
    }
}
复制代码

首先静态导入PowerMock的方法:

import static org.powermock.api.mockito.PowerMockito.*;

而后在使用junit4的测试类上作以下声明:

@RunWith(PowerMockRunner.class)
@PrepareForTest( { Class2Mocked.class })

例3.1 模拟静态方法

复制代码
@Test
public void testMockStaticMethod() {
    mockStatic(Class2Mocked.class);
    when(Class2Mocked.getDouble(1)).thenReturn(3);

    int actual = Class2Mocked.getDouble(1);
    assertEquals(3, actual);

    verifyStatic();
    Class2Mocked.getDouble(1);
}
复制代码

 

PowerMockit的局域模拟使用方式和mockito相似(毕竟是扩展mockito),但强大之处在于能够模拟private方法,普通方法和final方法。模拟普通方法和final方法的方式与模拟private方法如出一辙,现以模拟private方法为例。

例3.2 模拟私有方法(doCallRealMethod方式)

复制代码
@Test
public void testMockPrivateMethod() throws Exception {
    Class2Mocked obj = mock(Class2Mocked.class);
    
    when(obj, "multiply3", 1).thenReturn(4);
    doCallRealMethod().when(obj).getTripleString(1);
    
    String actual = obj.getTripleString(1);
    assertEquals("4", actual);
    
    verifyPrivate(obj).invoke("multiply3", 1); 
}
复制代码

例3.3 模拟私有方法(spy方式)

复制代码
@Test
public void testMockPrivateMethod2() throws Exception {
    Class2Mocked obj = spy(new Class2Mocked());
    when(obj, "multiply3", 1).thenReturn(4);

    String actual = obj.getTripleString(1);
    assertEquals("4", actual);

    verifyPrivate(obj).invoke("multiply3", 1); 
}
复制代码

 

除此以外,PowerMock也能够模拟构造方法,以下所示:

例3.4 模拟构造方法

复制代码
@Test 
public void testStructureWhenPathDoesntExist() throws Exception { 
    final String directoryPath = "mocked path"; 

    File directoryMock = mock(File.class); 

    whenNew(File.class).withArguments(directoryPath).thenReturn(directoryMock); 
    when(directoryMock.exists()).thenReturn(true); 

    File file=new File(directoryPath);
    assertTrue(file.exists()); 

    verifyNew(File.class).withArguments(directoryPath); 
    verifyPrivate(directoryMock).invoke("exists");
}
复制代码

 

4.Jmockit

JMockit 是一个轻量级的mock框架是用以帮助开发人员编写测试程序的一组工具和API,该项目彻底基于 Java 5 SE 的 java.lang.instrument 包开发,内部使用 ASM 库来修改Java的Bytecode。

Jmockit功能和PowerMock相似,某些功能甚至更为强大,但我的感受其代码的可读性并不强。

Jmockit能够在https://code.google.com/p/jmockit/上下载,使用maven的话添加以下依赖便可:

复制代码
<dependency>
  <groupId>com.googlecode.jmockit</groupId>
  <artifactId>jmockit</artifactId>
  <version>1.0</version>
  <scope>test</scope>
</dependency>
复制代码

Jmockit也能够分类为非局部模拟与局部模拟,区分在于Expectations块是否有参数,有参数的是局部模拟,反之是非局部模拟。而Expectations块通常由Expectations类和NonStrictExpectations类定义。用Expectations类定义的,则mock对象在运行时只能按照 Expectations块中定义的顺序依次调用方法,不能多调用也不能少调用,因此能够省略掉Verifications块;而用NonStrictExpectations类定义的,则没有这些限制,因此若是须要验证,则要添加Verifications块。

如今举例说明Jmockit的用法:

例4.1 非局部模拟Expectations类定义

复制代码
@Mocked  //用@Mocked标注的对象,不须要赋值,jmockit自动mock
Class1Mocked obj;

@Test
public void testMockNormalMethod1() {
    new Expectations() {
        {
            obj.hello("z3");
            returns("hello l4", "hello w5");
            obj.hello("张三");
            result="hello 李四";
        }
    };

    assertEquals("hello l4", obj.hello("z3"));
    assertEquals("hello w5", obj.hello("z3"));
    assertEquals("hello 李四", obj.hello("张三"));

    try {
        obj.hello("z3");
    } catch (Throwable e) {
        System.out.println("第三次调用hello(\"z3\")会抛出异常");
    }
    try {
        obj.show();
    } catch (Throwable e) {
        System.out.println("调用没有在Expectations块中定义的方法show()会抛出异常");
    }
}
复制代码

例4.2 非局部模拟 NonStrictExpectations类定义

复制代码
public void testMockNormalMethod2() {
    new NonStrictExpectations() {
        {
            obj.hello("z3");
            returns("hello l4", "hello w5");
        }
    };

    assertEquals("hello l4", obj.hello("z3"));
    assertEquals("hello w5", obj.hello("z3"));
    assertEquals("hello w5", obj.hello("z3"));// 会返回在NonStrictExpectations块中定义的最后一个返回值
    obj.show();

    new Verifications() {
        {
            obj.hello("z3");
            times = 3;
            obj.show();
            times = 1;
        }
    };
}
复制代码

运行这个测试会发现show()方法没有在控制台输出打印语句,说明是Jmockit对show方法也进行了默认mock。

 

例4.3 局部模拟

复制代码
@Test
public void testMockNormalMethod() throws IOException {
    final Class1Mocked obj = new Class1Mocked();//也能够不用@Mocked标注,但须要final关键字
    new NonStrictExpectations(obj) {
        {
            obj.hello("z3");
            result = "hello l4";
        }
    };

    assertEquals("hello l4", obj.hello("z3"));
    assertEquals("hello 张三", obj.hello("张三"));

    new Verifications() {
        {
            obj.hello("z3");
            times = 1;
            obj.hello("张三");
            times = 1;
        }
    };
}
复制代码

运行这个测试发现hello("z3")返回由Expectations块定义的值,但hello("张三")执行的是实际的代码。

 

例4.4 模拟静态方法

复制代码
@Test
public void testMockStaticMethod() {
    new NonStrictExpectations(Class2Mocked.class) {
        {
            Class2Mocked.getDouble(1);
            result = 3;
        }
    };

    assertEquals(3, Class2Mocked.getDouble(1));

    new Verifications() {
        {
            Class2Mocked.getDouble(1);
            times = 1;
        }
    };
}
复制代码

 

例4.5 模拟私有方法

复制代码
@Test
public void testMockPrivateMethod() throws Exception {
    final Class2Mocked obj = new Class2Mocked();
    new NonStrictExpectations(obj) {
        {
            this.invoke(obj, "multiply3", 1);
            result = 4;
        }
    };

    String actual = obj.getTripleString(1);
    assertEquals("4", actual);

    new Verifications() {
        {
            this.invoke(obj, "multiply3", 1);
            times = 1;
        }
    };
}
复制代码

例4.6 设置私有属性的值

假设有一个类须要被模拟的类以下:

复制代码
public class Class3Mocked {
    private String name = "name_init";

    public String getName() {
        return name;
    }
    
    private static String className="Class3Mocked_init";
    
    public static String getClassName(){
        return className;
    }
    
    public static int getDouble(int i){
        return i*2;
    }
    
    public int getTriple(int i){
        return i*3;
    }
}
复制代码

以下能够设置私有属性的值:

复制代码
@Test
public void testMockPrivateProperty() throws IOException {
    final Class3Mocked obj = new Class3Mocked();
    new NonStrictExpectations(obj) {
        {
            this.setField(obj, "name", "name has bean change!");
        }
    };

    assertEquals("name has bean change!", obj.getName());
}
复制代码

例4.7 设置静态私有属性的值

复制代码
@Test
public void testMockPrivateStaticProperty() throws IOException {
    new NonStrictExpectations(Class3Mocked.class) {
        {
            this.setField(Class3Mocked.class, "className", "className has bean change!");
        }
    };

    assertEquals("className has bean change!", Class3Mocked.getClassName());
}
复制代码

例4.8 改写普通方法的内容

复制代码
@Test
public void testMockNormalMethodContent() throws IOException {
    final Class3Mocked obj = new Class3Mocked();
    new NonStrictExpectations(obj) {
        {
            new MockUp<Class3Mocked>() {
                @Mock
                public int getTriple(int i) {
                    return i * 30;
                }
            };
        }
    };

    assertEquals(30, obj.getTriple(1));
    assertEquals(60, obj.getTriple(2));
}
复制代码

例4.9 改写静态方法的内容

若是要改写Class3Mocked类的静态getDouble方法,则须要新建一个类含有与getDouble方法相同的函数声明,而且用@Mock标注,以下:

复制代码
public class Class4Mocked {
    @Mock
    public static int getDouble(int i){
        return i*20;
    }
}
复制代码

以下便可改写:

复制代码
@Test
public void testDynamicMockStaticMethodContent() throws IOException {
    Mockit.setUpMock(Class3Mocked.class, Class4Mocked.class);

    assertEquals(20, Class3Mocked.getDouble(1));
    assertEquals(40, Class3Mocked.getDouble(2));
}
复制代码
相关文章
相关标签/搜索