单元测试系列:Mock工具之Mockito实战

更多原创测试技术文章同步更新到微信公众号 :三国测,敬请扫码关注我的的微信号,感谢!html

原文连接:http://www.cnblogs.com/zishi/p/6780719.htmljava

在实际项目中写单元测试的过程当中咱们会发现须要测试的类有不少依赖,这些依赖项又会有依赖,致使在单元测试代码里几乎没法完成构建,尤为是当依赖项还没有构建完成时会致使单元测试没法进行。为了解决这类问题咱们引入了Mock的概念,简单的说就是模拟这些须要构建的类或者资源,提供给须要测试的对象使用。业内的Mock工具备不少,也已经很成熟了,这里咱们将直接使用最流行的Mockito进行实战演练,完成mockito教程。git

Mock工具概述

1.1  Mockito简介

 

EasyMock 以及 Mockito 都由于能够极大地简化单元测试的书写过程而被许多人应用在本身的工做中,可是这两种 Mock 工具都不能够实现对静态函数、构造函数、私有函数、Final 函数以及系统函数的模拟,可是这些方法每每是咱们在大型系统中须要的功能。github

另外,关于更多Mockito2.0新特性,参考官方介绍文档,里边有关于为何不mock private的缘由,挺有意思的:数据库

https://github.com/mockito/mockito/wiki/What%27s-new-in-Mockito-2数组

1.2 Mockito准备工做

###Maven###微信

经过Maven管理的,须要在项目的Pom.xml中增长以下的依赖:网络

 <dependencies>

<dependency>

<groupId>org.mockito</groupId>

<artifactId>mockito-core</artifactId>

<version>2.7.19</version>

<scope>test</scope>

</dependency>

</dependencies>

 

 

在程序中能够import org.mockito.Mockito,而后调用它的static方法。session

Maven用户能够声明对mockito-core的依赖。 Mockito自动发布到Bintray的中心,并同步到Maven Central Repository。maven

特别提醒:使用手工依赖关系管理的Legacy构建可使用1. *“mockito-all”分发。 它能够从Mockito的Bintray存储库或Bintray的中心下载。 在可是Mockito 2. * “mockito-all”发行已经中止,Mockito 2以上版本使用“mockito-core”。

官网下载中心:

http://search.maven.org/#search|gav|1|g%3A%22org.mockito%22%20AND%20a%3A%22mockito-core%22

目前最新版本为2.7.19,因为公司网络网关问题,最好是去官网手工下载。

另外Mockito须要Junit配合使用,在Pom文件中一样引入:

<dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
      </dependency>

而后为了使代码更简洁,最好在测试类中导入静态资源,还有为了使用经常使用的junit关键字,也要引入junit的两个类Before和Test:

import static org.mockito.Mockito.*;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;

1.3 模拟对象

建立 Mock 对象的语法为 mock(class or interface)。

Mock 对象的建立

mock(Class classToMock);
 mock(Class classToMock, String name)
 mock(Class classToMock, Answer defaultAnswer)
 mock(Class classToMock, MockSettings mockSettings)
 mock(Class classToMock, ReturnValues returnValues)

能够对类和接口进行mock对象的建立,建立时能够为mock对象命名。对mock对象命名的好处是调试的时候容易辨认mock对象。

 

Mock对象的指望行为和返回值设定

假设咱们建立了LinkedList类的mock对象:

 LinkedList mockedList = mock(LinkedList.class);

1.4 设置对象调用的预期返回值

经过 when(mock.someMethod()).thenReturn(value) 来设定 Mock 对象某个方法调用时的返回值。咱们能够看看源码中关于thenReturn方法的注释:

 

使用when(mock.someMethod()).thenThrow(new RuntimeException) 的方式来设定当调用某个方法时抛出的异常。

 

以及Answer:

 

 

Answer 是个泛型接口。到调用发生时将执行这个回调,经过  Object[] args = invocation.getArguments();能够拿到调用时传入的参数,经过 Object mock = invocation.getMock();能够拿到mock对象。

有些方法可能接口的参数为一个Listener参数,若是咱们使用Answer打桩,咱们就能够获取这个Listener,而且在Answer函数中执行对应的回调函数,这对咱们了解函数的内部执行过成有很大的帮助。

使用doThrow(new RuntimeException(“clear exception”)).when(mockedList).clear();mockedList.clear();的方式Mock没有返回值类型的函数:

doThrow(new RuntimeException()).when(mockedList).clear();

//将会 抛出 RuntimeException:

mockedList.clear();

这个实例表示当执行到mockedList.clear()时,将会抛出RuntimeException。其余的doXXX执行与它相似。

例如 : doReturn()|doThrow()| doAnswer()|doNothing()|doCallRealMethod() 系列方法。

 

Spy函数:

你能够为真实对象建立一个监控(spy)对象,当你使用这个spy对象时,真实的对象也会被调用,除非它的函数被打桩。你应该尽可能少的使用spy对象,使用时也须要当心,例如spy对象能够用来处理遗留代码,Spy示例以下:

List list = new LinkedList();

//监控一个真实对象

List spy = spy(list);

//你能够为某些函数打桩

when(spy.size()).thenReturn(100);

//使用这个将调用真实对象的函数

spy.add("one");

spy.add("two");

//打印"one"

System.out.println(spy.get(0));

//size() 将打印100

System.out.println(spy.size());

//交互验证

verify(spy).add("one"); 
verify(spy).add("two");

理解监控真实对象很是重要,有时,在监控对象上使用when(Object)来进行打桩是不可能或者不切实际的。由于,当使用监控对象时,请考虑用doReturn、Answer、Throw()函数组来进行打桩,例如:

List list = new LinkedList();

List spy = spy(list);

//这是不可能的: 由于调用spy.get(0)时会调用真实对象的get(0)函数,此时会发生

//IndexOutOfBoundsException异常,由于真实对象是空的 when(spy.get(0)).thenReturn("foo");

//你须要使用 doReturn() 来打桩

doReturn("foo").when(spy).get(0);

Mockito并不会为真实的对象代理函数调用,实际上它会复制真实对象,所以,若是你保留了真实对象而且与之交互,不要指望监控对象获得正确的结果。当你在监控对象上调用一个没有stub函数时,并不会调用真实对象的对应函数,你不会在真实对象上看到任何效果。

1.5 验证被测试类方法

Mock 对象一旦创建便会自动记录本身的交互行为,因此咱们能够有选择的对它的 交互行为进行验证。在 Mockito 中验证 Mock 对象交互行为的方法是 verify(mock).someMethod(…)。最后 Assert() 验证返回值是否和预期同样。

1.6 Demo

 从网上找来一个最简单的代码实例,下面以具体代码演示如何使用Mockito,代码有三个类,分别以下:

Person类:

public class Person {
    private final int id; 
    private final String name; 
    public Person(int id, String name) { 
        this.id = id; 
        this.name = name; 
        } 
    public int getId() { 
        return id; 
        } 
    public String getName() { 
        return name; 
        }
}

PersonDao类:

public interface PersonDao {
    Person getPerson(int id); 
    boolean update(Person person); 
    }

PersonService类:

 

public class PersonService {
    private final PersonDao personDao; 
    public PersonService(PersonDao personDao) { 
        this.personDao = personDao; 
        } 
    public boolean update(int id, String name) { 
        Person person = personDao.getPerson(id); 
        if (person == null) 
        { return false; } 
        Person personUpdate = new Person(person.getId(), name); 
        return personDao.update(personUpdate); 
        }
}

 

仍然使用Junit自动生成测试类或者手工新建测试类:

 

 

测试代码生成后,将默认assertfail的删掉,输入如下两个测试方法:

import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;


public class PersonServiceTest {

    private PersonDao     mockDao;
    private PersonService personService;

    @Before
    public void setUp() throws Exception {
        //模拟PersonDao对象
        mockDao = mock(PersonDao.class);
        when(mockDao.getPerson(1)).thenReturn(new Person(1, "Person1"));
        when(mockDao.update(isA(Person.class))).thenReturn(true);

        personService = new PersonService(mockDao);
    }

    @Test
    public void testUpdate() throws Exception {
        boolean result = personService.update(1, "new name");
        assertTrue("must true", result);
        //验证是否执行过一次getPerson(1)
        verify(mockDao, times(1)).getPerson(eq(1));
        //验证是否执行过一次update
        verify(mockDao, times(1)).update(isA(Person.class));
    }

    @Test
    public void testUpdateNotFind() throws Exception {
        boolean result = personService.update(2, "new name");
        assertFalse("must true", result);
        //验证是否执行过一次getPerson(1)
        verify(mockDao, times(1)).getPerson(eq(1));
        //验证是否执行过一次update
        verify(mockDao, never()).update(isA(Person.class));
    }
}

注意:咱们对PersonDAO进行mock,而且设置stubbing,stubbing设置以下:

  • 当getPerson方法传入1的时候,返回一个Person对象,不然默认返回空
  • 当调update方法的时候,返回true

这里使用了两个参数匹配器:

  isA():Object argument that implements the given class.

  eq():int argument that is equal to the given value

注:Mockito使用verify去校验方法是否被调用,而后使用isA和eq这些内置的参数匹配器能够更加灵活,

关于参数匹配器的详细使用请参考官网文档:https://static.javadoc.io/org.mockito/mockito-core/2.25.0/org/mockito/ArgumentMatchers.html

因为官网的代码和解释很是详细,此处就再也不赘述。

 

仍然调用Junit执行单元测试代码,结果如图所示:

验证了两种状况:

  • 更新id为1的Person的名字,预期:能在DAO中找到Person并更新成功
  • 更新id为2的Person的名字,预期:不能在DAO中找到Person,更新失败

这里也能够查看Eclipse抛出的异常信息:

Argument(s) are different! Wanted:

personDao.getPerson(1);

-> at PersonServiceTest.testUpdateNotFind(PersonServiceTest.java:41)

Actual invocation has different arguments:

personDao.getPerson(2);

-> at PersonService.update(PersonService.java:8)

2 单元测试与覆盖率

一、Junit 二、JaCoCo 三、EclEmma

2 覆盖率

覆盖率以下图显示:

覆盖率仍然使用JaCoCo和EclEmma:

l  未覆盖代码标记为红色

l  已覆盖代码会标记为绿色

l  部分覆盖的代码标记为黄色

颜色也能够在Eclipse中自定义设置:

在Eclipse下方的状态栏窗口,有一栏“Coverage”,点击能够显示详细的代码覆盖率:

如何导出为Html格式的Report:

在Eclipse下方的Coverage栏鼠标右键选择“Export Session…”,在弹出窗口中选择export的目标为“Coverage Report”以下图:

点击“Next”按钮后,在接下来的弹出窗口选择须要导出的session,Format

类型选择“HTML report”,导出位置暂时选择为桌面,都选择以后点击“Finish”按钮就生成好了。

在桌面上找到一个叫作index.html的页面就是刚刚生成好的Coverage Report:

点击文件夹能够进入目录,进一步查看子文件的覆盖率:

 

 

附录:参考文档一览

 

Mockito官网: http://site.mockito.org/

5分钟了解Mockito:http://liuzhijun.iteye.com/blog/1512780

Mockito简单介绍及示例:http://blog.csdn.net/huoshuxiao/article/details/6107835

Mockito浅谈:http://www.jianshu.com/p/77db26b4fb54

单元测试利器-Mockito 中文文档:http://blog.csdn.net/bboyfeiyu/article/details/52127551

Mockito使用指南 :http://blog.csdn.net/shensky711/article/details/52771493

JUnit+Mockito 单元测试(二):http://blog.csdn.net/zhangxin09/article/details/42422643

 

感谢阅读!做者原创技术文章,转载请注明出处

 

其余推荐相关阅读:

单元测试系列之一:如何使用JUnit、JaCoCo和EclEmma提升单元测试覆盖率

测试系列之二:Mock工具Jmockit实战

单元测试系列之三:JUnit单元测试规范

单元测试系列之四:Sonar平台中项目主要指标以及代码坏味道详解

单元测试系列之五:Mock工具之Mockito实战

单元测试系列之六:JUnit5 技术前瞻

单元测试系列之七:Sonar 数据库表关系整理一(rule相关)

单元测试系列之八:Sonar 数据库表关系整理一(续)

单元测试系列之九:Sonar 经常使用代码规则整理(一)

单元测试系列之十:Sonar 经常使用代码规则整理(二)

单元测试系列之十一:Jmockit之mock特性详解

相关文章
相关标签/搜索