Mock 模拟测试简介及 Mockito 使用入门

Mock 是什么

mock 测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。这个虚拟的对象就是mock对象。mock对象就是真实对象在调试期间的代替品。

简单的看一张图

我们在测试类 A 时,类 A 需要调用类 B 和类 C,而类 B 和类 C 又需要调用其他类如 D、E、F 等,假如类 D、E、F 构造很耗时又或者调用很耗时的话是非常不便于测试的(比如是 DAO 类,每次访问数据库都很耗时)。所以我们引入 Mock 对象。

如上图,我们将类 B 和类 C 替换成 Mock 对象,在调用类 B 和类 C 的方法时,用 Mock 对象的方法来替换(当然我们要自己设定参数和期望结果)而不用实际去调用其他类。这样测试效率会高很多。

一句话为什么要 Mock:因为我们实际编写程序都不会是一个简单类,而是有着复杂依赖关系的类,Mock 对象让我们在不依赖具体对象的情况下完成测试。


Mock 的关键点

Mock 对象

模拟对象的概念就是我们想要创建一个可以替代实际对象的对象,这个模拟对象要可以通过特定参数调用特定的方法,并且能返回预期结果。

Stub(桩)

桩指的是用来替换具体功能的程序段。桩程序可以用来模拟已有程序的行为或是对未完成开发程序的一种临时替代。

比如我们有一个获取温度的函数。

public double getTemperature(String Position) {
    double ret = TemperatureRead(Position);
}

但是 TemperatureRead 函数要调用具体的硬件设备,而硬件设备没准备好。

我们可以用 Stub 来替换

public double TemperatureRead(String position) {
    return 28;
}

Stub:For replacing a method with code that returns a specified result

Mock:A stub with an expectations that the method gets called


设置预期

通过设置预期明确 Mock 对象执行时会发生什么,比如返回特定的值、抛出一个异常、触发一个事件等,又或者调用一定的次数。


验证预期的结果

设置预期和验证预期是同时进行的。设置预期在调用测试类的函数之前完成,验证预期则在它之后。所以,首先你设定好预期结果,然后去验证你的预期结果是否正确。


Mock 的好处是什么

提前创建测试; TDD(测试驱动开发)

这是个最大的好处吧。如果你创建了一个Mock那么你就可以在service接口创建之前写Service Tests了,这样你就能在开发过程中把测试添加到你的自动化测试环境中了。换句话说,模拟使你能够使用测试驱动开发。


团队可以并行工作

这类似于上面的那点;为不存在的代码创建测试。但前面讲的是开发人员编写测试程序,这里说的是测试团队来创建。当还没有任何东西要测的时候测试团队如何来创建测试呢?模拟并针对模拟测试!这意味着当service借口需要测试时,实际上QA团队已经有了一套完整的测试组件;没有出现一个团队等待另一个团队完成的情况。这使得模拟的效益型尤为突出了。


你可以创建一个验证或者演示程序

由于Mocks非常高效,Mocks可以用来创建一个概念证明,作为一个示意图,或者作为一个你正考虑构建项目的演示程序。这为你决定项目接下来是否要进行提供了有力的基础,但最重要的还是提供了实际的设计决策。


为无法访问的资源编写测试

这个好处不属于实际效益的一种,而是作为一个必要时的“救生圈”。有没有遇到这样的情况?当你想要测试一个service接口,但service需要经过防火墙访问,防火墙不能为你打开或者你需要认证才能访问。遇到这样情况时,你可以在你能访问的地方使用MockService替代,这就是一个“救生圈”功能。


Mock 可以交给用户

在有些情况下,某种原因你需要允许一些外部来源访问你的测试系统,像合作伙伴或者客户。这些原因导致别人也可以访问你的敏感信息,而你或许只是想允许访问部分测试环境。在这种情况下,如何向合作伙伴或者客户提供一个测试系统来开发或者做测试呢?最简单的就是提供一个mock,无论是来自于你的网络或者客户的网络。soapUI mock非常容易配置,他可以运行在soapUI或者作为一个war包发布到你的java服务器里面。


隔离系统

有时,你希望在没有系统其他部分的影响下测试系统单独的一部分。由于其他系统部分会给测试数据造成干扰,影响根据数据收集得到的测试结论。使用mock你可以移除掉除了需要测试部分的系统依赖的模拟。当隔离这些mocks后,mocks就变得非常简单可靠,快速可预见。这为你提供了一个移除了随机行为,有重复模式并且可以监控特殊系统的测试环境。


Mockito

介绍

Mockito 是一个简单的流行的 Mock 框架。它能够帮我们创建 Mock 对象,保持单元测试的独立性。

使用它只需要在 Maven 中添加依赖即可。

<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all -->
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-all</artifactId>
        <version>2.0.2-beta</version>
    </dependency>

创建 Mock 对象

通过方法创建

class CreateMock {
    @Before
    public void setup() {
        mockUserDao = mock(UserDao.class);
        userService = new UserServiceImpl();
        userService.setUserDao(mockUserDao);
    }
}

通过注解创建

class CreateMock {
    @Mock
    UserDao mockUserDao;

    @InjectMocks
    private UserServiceImpl userService;  

    @Before
    public void setUp() {
        //初始化对象的注解
        MockitoAnnotations.initMocks(this);
    }
}

一个简单的例子:

package Mockito;

import org.junit.Assert;
import org.junit.Test;
import java.util.List;

import static org.mockito.Mockito.*;

public class MyTest {
    @Test
    public void myTest() {
        /* 创建 Mock 对象 */
        List list = mock(List.class);
        /* 设置预期,当调用 get(0) 方法时返回 "111" */
        when(list.get(0)).thenReturn("111");

        Assert.assertEquals("asd", 1, 1);
        /* 设置后返回期望的结果 */
        System.out.println(list.get(0));
        /* 没有设置则返回 null */
        System.out.println(list.get(1));

        /* 对 Mock 对象设置无效 */
        list.add("12");
        list.add("123");
        /* 返回之前设置的结果 */
        System.out.println(list.get(0));
        /* 返回 null */
        System.out.println(list.get(1));
        /* size 大小为 0 */
        System.out.println(list.size());

        /* 验证操作,验证 get(0) 调用了 2 次 */
        verify(list, times(2)).get(0);

        /* 验证返回结果 */
        String ret = (String)list.get(0);
        Assert.assertEquals(ret, "111");
    }
}

从上面代码能看出一般使用的步骤:

设置方法预期返回

通过 when(list.get(0)).thenReturn(“111”); 来设置当调用 list.get(0) 时返回 “111”,该方法就是 Stub,替换我们实际的操作。

验证方法调用

该方法验证 get(0) 方法调用的次数 verify(list, times(2)).get(0);,还可以设置是否调用过,调用时间等等。

验证返回值

Assert.assertEquals(ret, “111”); 方法验证 Mock 对象方法调用后的返回值是否达到预期。


Mockito 使用

设置 Mock 对象期望和返回值

/* 表示第一次调用 someMethod() 返回 value1 第二次调用返回 value2 */
when(mock.someMethod()).thenReturn(value1).thenReturn(value2);
when(mock.someMethod()).thenReturn(value1, value2);
/* 也可以设置两次 */
when(mock.someMethod()).thenReturn(value1);
when(mock.someMethod()).thenReturn(value2);

另外一种写法 doReturn()

/* 表示第一次调用 someMethod() 返回 value1 第二次调用返回 value2 */
doReturn(value1).doReturn(value2).when(mock).someMethod();
/* 若返回 void,则设置为 doNothing() */
doNothing().when(mock).someMethod();

对方法设定返回异常

/* 当调用 someMethod() 方法时会抛出异常 */
when(mock.someMethod()).thenThrow(new RuntimeException());
/* 对 void 方法设定 */
doThrow(new RuntimeException()).when(mock).someMethod();  

参数匹配器

我们不一定要固定 Stub 调用时的参数,如 get(0)。可以通过参数匹配器来调用。

when(list.get(anyInt())).thenReturn("hello");

Mock 对象的行为验证

package Mockito;

import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.testng.annotations.Test;

import java.util.List;
import static org.mockito.Mockito.*;

/**
 * Created by andy.wwh on 2016/7/18.
 */
public class Behavior {
    @Test
    public void behaviorCheck() {
        List mock1 = mock(List.class);
        List mock2 = mock(List.class);

        /* 设置预期 */
        when(mock1.get(0)).thenReturn("hello world");
        when(mock1.get(1)).thenReturn("hello world");
        when(mock2.get(0)).thenReturn("hello world");
        mock1.get(0);

        /* 验证方法调用一次 */
        verify(mock1).get(0);
        mock1.get(0);
        /* 验证方法调用两次 */
        verify(mock1, times(2)).get(0);
        /* 验证方法从未被调用过 */
        verify(mock2, never()).get(0);
        /* 验证方法 100 毫秒内调用两次 */
        verify(mock1, timeout(100).times(2)).get(anyInt());

        /* 设置方法调用顺序 */
        InOrder inOrder = inOrder(mock1, mock2);
        inOrder.verify(mock1, times(2)).get(0);
        inOrder.verify(mock2, never()).get(1);

        /*  查询是否存在被调用,但未被 verify 验证的方法 */
        verifyNoMoreInteractions(mock1, mock2);
        /* 验证 Mock 对象是否没有交发生 */
        verifyZeroInteractions(mock1, mock2);

        /* 参数捕获器 */
        ArgumentCaptor<Integer> argumentCaptor = ArgumentCaptor.forClass(Integer.class);
        verify(mock1, times(2)).get(argumentCaptor.capture());
        System.out.println("argument:" + argumentCaptor.getValue());
    }
}

验证调用次数

verify(mock1, timeout(100).times(2)).get(anyInt());

除了代码中的方法,Mockito 还提供了

- never() 没有被调用,相当于times(0)

- atLeast(N) 至少被调用N次

- atLeastOnce() 相当于atLeast(1)

- atMost(N) 最多被调用N次

超时验证

通过 timeout 我们可以进行验证程序执行时间是否符合规则。

方法调用顺序

Inorder 可以验证方法调用的顺序

verifyNoMoreInteractions 和 verifyZeroInteractions

verifyNoMoreInteractions:查询是否存在被调用,但未被 verify 验证的方法

verifyZeroInteractions:verifyZeroInteractions

ArgumentCaptor 参数捕获器

可在验证时对方法的参数进行捕获,最后验证捕获的参数值。如果方法有多个参数都要捕获验证,那就需要创建多个ArgumentCaptor对象处理。


Spy 对象验证

Mock 操作的全是虚拟对象。即使我们设置了 when(list.get(0)).thenReturn(1),我们调用如 size() 方法返回的还是 0。Mockito 还给我们提供了一种对真实对象操作的方法——Spy

做一个简单的比较:

package Mockito;

import org.testng.annotations.Test;

import java.util.List;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

/**
 * Created by andy.wwh on 2016/7/18.
 */
public class MockObject {
    @Test
    public void MockTest() {
        List list = mock(List.class);

        when(list.get(0)).thenReturn("hello world");

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

        System.out.println(list.size());
    }
}

package Mockito;

import org.testng.annotations.Test;

import java.util.LinkedList;
import java.util.List;

import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

/**
 * Created by andy.wwh on 2016/7/18.
 */
public class MockObject {
    @Test
    public void MockTest() {
        /* 创建真实对象 */
        List list = new LinkedList();
        List spy = spy(list);

        spy.add("hello");

        when(spy.get(0)).thenReturn("hello world");

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

看个官网的例子:

@Test
public void spyTest() {
    List list = new LinkedList();
    List spy = spy(list);
    // optionally, you can stub out some methods:
    when(spy.size()).thenReturn(100);
    // using the spy calls real methods
    spy.add("one");
    spy.add("two");
    // prints "one" - the first element of a list
    System.out.println(spy.get(0));
    // size() method was stubbed - 100 is printed
    System.out.println(spy.size());
    // optionally, you can verify
    verify(spy).add("one");
    verify(spy).add("two");
}  


参考:

Mockito 初探

Mockito:一个强大的用于 Java 开发的模拟测试框架

时间: 2024-08-10 10:46:42

Mock 模拟测试简介及 Mockito 使用入门的相关文章

springboot2.0入门(四)----mock模拟测试+单元测试

一.本节主要记录模拟测试.单元测试: 二.mock 测试 1.1什么是Mock? 在面向对象程序设计中,模拟对象(英语:mock object,也译作模仿对象)是以可控的方式模拟真实对象行为的假的对象.比如:对象B依赖于对象A,但是A代码还没写是一个空类空方法不能用,我们来mock一个假的A来完成测试. /** * @author Levi * @date 2019/10/4 21:09 */ //@Transactional @Slf4j @SpringBootTest public clas

【原创】junit4中Assert断言的使用以及Mockito框架mock模拟对象的简单使用

编写测试代码时,我们总会有我们对被测方法自己预期的值,以及调用被测方法后返回的真实执行后的值.需要断言这两个值是否相等.抛出异常.hash码等等情况... 这里博主主要介绍一下简单的断言和mock.如果已经对junit测试有过相对了解的,请略过这篇文章. 下面是我准备的节点类: 1 package demo; 2 3 /** 4 * @author Lcc 5 * 6 */ 7 public class Node { 8 private int value; 9 10 public Node(i

使用PowerMockito和Mockito进行模拟测试,包括静态方法测试,私有方法测试等

依赖:这个很重要,不同版本用法也有点区别: <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-all</artifactId> <version>2.0.2-beta</version> <scope>test</scope> </dependency> <dependency> <group

Android单元测试与模拟测试详解

测试与基本规范 为什么需要测试? 为了稳定性,能够明确的了解是否正确的完成开发. 更加易于维护,能够在修改代码后保证功能不被破坏. 集成一些工具,规范开发规范,使得代码更加稳定( 如通过 phabricator differential 发diff时提交需要执行的单元测试,在开发流程上就可以保证远端代码的稳定性). 2. 测什么? 一般单元测试: 列出想要测试覆盖的异常情况,进行验证. 性能测试. 模拟测试: 根据需求,测试用户真正在使用过程中,界面的反馈与显示以及一些依赖系统架构的组件的应用测

再说模拟测试

模拟一个方法 Controller: namespace WebApplication1.Controllers { public class TestController : Controller { public TestController(IService service) { this.Service = service; } public IService Service { get; set; } // GET: Test public virtual ActionResult I

模拟测试(vj)

做这份模拟测试,已经崩溃了,英文看不懂,题意理解错.到结束了只a了第一题,人生陷入了低谷,于是花了一天的时间终于把不会的弄明白了,在这里写一份总结~ T1,简单的模拟,如果打枪打中一支鸟,将这个位置设为0,并向两边扩散,注意这个位置一定要有鸟. 代码~ #include<bits/stdc++.h> using namespace std; int a[30000]; int n,m; int main() { cin>>n; for(int i=1;i<=n;i++) ci

微信在线信息模拟测试工具(基于Senparc.Weixin.MP)

目前为止似乎还没有看到过Web版的普通消息测试工具(除了官方针对高级接口的),现有的一些桌面版的几个测试工具也都是使用XML直接请求,非常不友好,我们来尝试做一个“面向对象”操作的测试工具. 测试工具在线DEMO:http://weixin.senparc.com/SimulateTool Senparc.Weixin.MP是一个开源的微信SDK项目,地址:https://github.com/JeffreySu/WeiXinMPSDK (其中https://github.com/Jeffrey

其他主流开源硬件简介BeagleBone Black快速入门

其他主流开源硬件简介BeagleBone Black快速入门 1.3 其他主流开源硬件简介 开源硬件种类繁多,但主要有两款开源硬件常与BeagleBone比较.它们就是Arduino和Raspberry Pi(即树莓派).之所以常拿他们来比较,是因为他们有共同点也有不同点.在笔者看来,他们之间可以互相补充,但是均是不可替代的.下面就来简单介绍一下这两种开源硬件本文选自BeagleBone Black快速入门教程. 1.3.1  Arduino Arduino常被用来称呼Arduino的硬件控制器

css Hack,用IE11模拟测试的,条件注释要找真IE去测,模拟的无效

<!DOCTYPE html> <!--[if lt IE 7 ]> <html class="ie6 ie"> <![endif]--> <!--[if IE 7 ]> <html class="ie7 ie"> <![endif]--> <!--[if IE 8 ]> <html class="ie8 ie"> <![endif]