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

2023-11-16

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 开发的模拟测试框架

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • 插入最大日期(独立于数据库)

    在我的本地设置中 我使用一个简单的 H2 数据库 托管 解决方案将有另一个 类似但不相同 数据库 我需要将最大可能日期插入到日期时间列中 我尝试使用 Instant MAX 但是 这会导致列中出现 169104626 12 11 20 08
  • Java:如何从转义的 URL 获取文件?

    我收到了一个定位本地文件的 URL 事实上我收到的 URL 不在我的控制范围内 URL 按照 RFC2396 中的定义进行有效转义 如何将其转换为 Java File 对象 有趣的是 URL getFile 方法返回一个字符串 而不是文件
  • Android在排序列表时忽略大小写

    我有一个名为路径的列表 我目前正在使用以下代码对字符串进行排序 java util Collections sort path 这工作正常 它对我的 列表进行排序 但是它以不同的方式处理第一个字母的情况 即它用大写字母对列表进行排序 然后用
  • 比较两个文本文件的最快方法是什么,不将移动的行视为不同

    我有两个文件非常大 每个文件有 50000 行 我需要比较这两个文件并识别更改 然而 问题是如果一条线出现在不同的位置 它不应该显示为不同的 例如 考虑这个文件A txt xxxxx yyyyy zzzzz 文件B txt zzzzz xx
  • JAVA - Xuggler - 组合 MP3 音频文件和 MP4 电影时播放视频

    使用 JAVA 和 Xuggler 以下代码组合 MP3 音频文件和 MP4 电影文件并输出组合的 mp4 文件 我希望在合并音频和视频文件时应自动播放输出视频文件 String inputVideoFilePath in mp4 Stri
  • java中如何连接字符串

    这是我的字符串连接代码 StringSecret java public class StringSecret public static void main String args String s new String abc s co
  • JavaFX 中具有自定义内容的 ListView

    How i can make custom ListView with JavaFx for my app I need HBox with image and 2 Labels for each line listView 您可以通过查看
  • 使用 AES SecretKey 的 Java KeyStore setEntry()

    我目前正在 Java 中开发一个密钥处理类 特别是使用 KeyStore 我正在尝试使用 AES 实例生成 SecretKey 然后使用 setEntry 方法将其放入 KeyStore 中 我已经包含了代码的相关部分 The KS Obj
  • 匿名类上的 NotSerializedException

    我有一个用于过滤项目的界面 public interface KeyValFilter extends Serializable public static final long serialVersionUID 7069537470113
  • Calendar.getInstance(TimeZone.getTimeZone("UTC")) 不返回 UTC 时间

    我对得到的结果真的很困惑Calendar getInstance TimeZone getTimeZone UTC 方法调用 它返回 IST 时间 这是我使用的代码 Calendar cal Two Calendar getInstance
  • Java 8 流 - 合并共享相同 ID 的对象集合

    我有一系列发票 class Invoice int month BigDecimal amount 我想合并这些发票 这样我每个月都会收到一张发票 金额是本月发票金额的总和 例如 invoice 1 month 1 amount 1000
  • 具有 java XSLT 扩展的数组

    我正在尝试使用 java 在 XSLT 扩展中使用数组 我收到以下错误 Caused by java lang ClassCastException org apache xpath objects XObject cannot be ca
  • Java整数双除法混淆[重复]

    这个问题在这里已经有答案了 方案1 int sum 30 double avg sum 4 result is 7 0 not 7 5 VS 方案2 int sum 30 double avg sum 4 0 Prints lns 7 5
  • 如何知道抛出了哪个异常

    我正在对我们的代码库进行审查 有很多这样的陈述 try doSomething catch Exception e 但我想要一种方法来知道 doSomething 抛出了哪个异常 在 doSomething 的实现中没有 throw 语句
  • Netty:阻止调用以获取连接的服务器通道?

    呼吁ServerBootstrap bind 返回一个Channel但这不是在Connected状态 因此不能用于写入客户端 Netty 文档中的所有示例都显示写入Channel从它的ChannelHandler的事件如channelCon
  • 替换后增量

    我自己已经有一个问题了 但我想扩展它后增量示例 https stackoverflow com questions 51308967 post increment with example char a D int b 5 System o
  • 为什么C++代码执行速度比java慢?

    我最近用 Java 编写了一个计算密集型算法 然后将其翻译为 C 令我惊讶的是 C 的执行速度要慢得多 我现在已经编写了一个更短的 Java 测试程序和一个相应的 C 程序 见下文 我的原始代码具有大量数组访问功能 测试代码也是如此 C 的
  • 为什么这个作业不起作用?

    我有课Results which extends ArrayList
  • 如何使用 JSch 将多行命令输出存储到变量中

    所以 我有一段很好的代码 我很难理解 它允许我向我的服务器发送命令 并获得一行响应 该代码有效 但我想从服务器返回多行 主要类是 JSch jSch new JSch MyUserInfo ui new MyUserInfo String
  • FileOutputStream.close() 中的设备 ioctl 不合适

    我有一些代码可以使用以下命令将一些首选项保存到文件中FileOutputStream 这是我已经写了一千遍的标准代码 FileOutputStream out new FileOutputStream file try BufferedOu

随机推荐

  • 云管平台 — vRealize Suite

    原文地址 https blogs vmware com china 2017 11 08 E4 BA 91 E7 AE A1 E5 B9 B3 E5 8F B0 vrealize suite vRealize Suite 是 vRealiz
  • 取余运算的意义

    取余运算的意义一般是给一个数一个界定范围 就比如m n 100 就限定了m的的范围只能是0 100 更形象来说 我们可以把它想象成一个圆环 我们扩大n 就像当于在0 100这个圈内打转 我们再稍微扩展一下 n 0 while true n
  • C/C++ C++20 格式化库 std::format

    说明 文本格式化库提供 printf 函数族的安全且可扩展的替用品 有意使之补充既存的 C I O 流库并复用其基础设施 例如对用户定义类型重载的流插入运算符 头文件 include
  • npm ERR! missing script dev

    刚刚npm install之后执行npm run dev 出现的报错信息npm ERR missing script dev 1 一种可能时vue init webpack的时候多建了一层文件夹 然后运行的时候没有找到package jso
  • MySQL--udf提权

    udf提权 udf user defined function 即 用户自定义函数 是通过添加新函数 对MYSQL的功能进行扩充 如何获得udf文件 将文件放到哪才能让mysql承认这个函数 函数功能 为什么这东西能提权 自定义函数指令是直
  • Netty-UDP协议

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 实现一个UDP应用关键的点 1 和tcp的不同 udp没有接收的说法 所以即使是接收端 也使用Bootstrap 2 指定channel为NioDatagramChanne
  • 汉字编码输入法综述

    2 汉字编码输入法综述 作者 戴石麟 sbxlm 126 com 本章打算分基础工作 理论研究和实用系统三个方面来对汉字编码输入技术的历史和现状进行综合评述 最后指出现有技术中存在的问题并预测今后技术的发展趋势 2 1基础工作 1974年8
  • java 导入自定义类

    eclipse导入很容易 昨天上课学了一下用记事本写java 导入自定义类 这就麻烦了 代码贴一下 方便操作 package tom jiafei public class SquareEquation double a b c doubl
  • 【SpringMVC】Jrebel 插件实现热部署与文件上传

    目录 一 JRebel 1 1 Jrebel介绍 1 2 Jrebel插件下载 1 3 Jrebel服务下载并启动 1 4 在线生成GUID 1 5 JRebel激活 1 6 相关设置 注意 二 文件上传 下载 2 1 导入pom依赖 2
  • MATLAB 拟合神经网络—— fitnet

    建立神经网络 语法 net fitnet hiddenSizes trainFcn hiddenSize 为隐藏层数 是一个行向量 分别表示从左到右的隐藏层神经元数 trainFcn 为训练函数 如下表所示 名称 函数 trainlm Le
  • go 进阶 go-zero相关: 三. go-zero 微服务基础示例

    目录 一 go zero 微服务基础 安装 ETCD 1 docker 安装运行etcd 2 windows 安装 etcd 二 go zero使用goctl命令创建一个普通的服务 三 go zero使用goctl命令创建一个rpc服务 1
  • python批量下载文件并压缩后上传到owncloud

    目录 1 首先获的一个保存url的文件 2 下载文件到服务器 3 将文件上传到owncloud 3 1 上传单个文件 3 2 上传多个文件 大文件拆分为小文件 推荐 摘要 笔者想下载东西到本地 直接下载速度超慢 一共需要下载1500张图 下
  • 每天进步一点点【图的深度优先遍历(DFS)】

    图是一种数据结构 其中节点可以具有零个或多个相邻元素 两个节点之间的连接称为边 节点也可以称为顶点 图分为三种 无向图 有向图 带权图 图的表示方式有两种 二维数组表示 邻接矩阵 链表表示 邻接表 邻接矩阵 邻接矩阵是表示图形中顶点之间相邻
  • 局域网使用kubeadm安装高可用k8s集群

    主机列表 ip 主机名 节点 cpu 内存 192 168 23 100 k8smaster01 master 2核 2G 192 168 23 101 k8smaster02 node 2核 2G 192 168 23 102 k8sma
  • 第三方支付自建商户池体系

    三方支付自建商户池体系通常指的是第三方支付机构自己搭建的商户池管理系统 商户池是指该支付机构所拥有的所有商户账户的集合 在支付领域 商户池的建立对于支付机构来说非常重要 它可以帮助支付机构更有效地管理商户 风控和支付流程 以下是自建商户池体
  • Animator的基本用法

    这里仅仅介绍Animator的一些基本的用法 说到Animator 最重要的最常用的的就是ObjectAnimator类 因为这个类可以对任意View的任意属性进行操作 首先以ImageView为例 以下所有的操作都针对ImageView
  • Node.js web3.js编译、部署智能合约

    Node js web3 js编译 部署智能合约 供参考脚本 https github com Saturday24 Smart Contracts Script 1 编译脚本 a install web3 solc fs path b 编
  • linux查看所有的进程及端口,linux查看所有进程和端口

    Linux下查看一个进程占用了哪个端口的方法 时候需要在Linux下查看一个进程占用了那个端口 但是只知道进程大致的名称 比如要查看hadoop的namenode在哪个端口上运行 以便在eclipse中连接 首先用ps命令查看进程的id 复
  • 校招算法题实在不会做,有没有关系?

    文章目录 前言 一 校招 二 时间复杂度 1 单层循环 2 双层循环 三 空间复杂度 四 数据结构 五 校招算法题实在不会做 有没有关系 六 英雄算法集训 前言 英雄算法联盟八月集训 已经接近尾声 九月算法集训将于 09月01日 正式开始
  • Mock 模拟测试简介及 Mockito 使用入门

    Mock 是什么 mock 测试就是在测试过程中 对于某些不容易构造或者不容易获取的对象 用一个虚拟的对象来创建以便测试的测试方法 这个虚拟的对象就是mock对象 mock对象就是真实对象在调试期间的代替品 简单的看一张图 我们在测试类 A