单元测试框架Mockito落地实践分享

news2025/4/4 13:40:29

一、序言

针对功能做测试的时候,我们经常会有单元测试和集成测试,在实际开发过程中发现有很多童鞋经常混淆这两个内容,在分享Mockito使用过程前先区分这两个概念。

二、测试分类和区别

所谓单元测试,其实就是对单个方法内部逻辑的测试,不涉及关联其他分层的代码逻辑测试,比如定义服务层UserService和Dao层UserDao,两个类分别存在save方法,并且前者在方法内部调用后者方法,那么对UserService的save方法进行单元测试只是针对内部逻辑测试,要屏蔽掉UserDao相关方法调用返回值影响。单元测试的特点就是执行速度快,单一的结构,不依赖其他容器。

而集成测试恰好与单元测试相反,它需要将关联类的行为纳入测试结果,便于测试系统各个组件集成后是否能运行正确,更多针对的是整个处理流程。

三、什么是Mock测试

Mock 测试是单元测试的一种形式,通过在测试过程中创建一个假的对象,避免为了测试一个方法,需要自行构建整个 Bean 的依赖链。比如下入,类 A 需要调用类 B 和类 C,而类 B 和类 C 又需要调用其他类如 D、E、F 等,假设类 D 是一个外部服务,那就会很很难测试,因为返回结果会直接受下游服务的影响,导致单元测试流程受阻。
在这里插入图片描述
而 Mock 测试,就是帮忙解决这个问题。它可以创建一个假的对象,替换掉真实的 Bean B 和 C,这样在方法调用时,实际上就会去调用 Mock 对象的方法,而 Mock 对象又可以设置我们自己的参数值和期望的返回值,让我们可以专注在自己的测试范围内,而不会受到其他的下游服务影响,从而提高整个单元测试的效率。引入 Mock 测试之后整个流程变化如下:
在这里插入图片描述

四、什么是 Mockito

Mockito 是一种 Java Mock 框架,他主要就是用来做 Mock 测试的,它可以模拟任何 Spring 管理的 Bean、方法返回值、异常抛出等等,同时也会记录调用这些模拟方法的参数、调用顺序,从而可以校验出这个 Mock 对象是否有被正确的顺序调用,以及按照期望的参数被调用。

比如 Mockito 可以在单元测试中模拟一个 Service 返回的数据,而不会真正去调用该 Service,这就是上面提到的 Mock 测试精神,也就是通过模拟一个假的 Service 对象,来快速的测试当前想要测试的类。目前在 Java 中主流的 Mock 测试工具有 Mockito、JMock、EasyMock等等,而 SpringBoot 目前内建的是 Mockito 框架。

五、单元测试编写

引入依赖,改依赖包含 JUnit 和 Mockito。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

创建 UserService 和 UserDao,UserService服务提供两个方法 getUserById() 和 insertUser(),分别调用 UserDao 的 getUserById() 和 insertUser() 两个方法,具体代码如下:

@Component
public class UserService {
    
    @Autowired
    private UserDao userDao;

    public User getUserById(Integer id) {
        return userDao.getUserById(id);
    }

    public Integer insertUser(User user) {
        return userDao.insertUser(user);
    }
}

定义 User 实体:

public class User {
    private Integer id;
    private String name;
    /**
    * 此处省略get/set方法
    **/
}

假设当前不使用 Mockito,测试代码大概如下:

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {

    @Autowired
    private UserService userService;

    @Test
    public void getUserById() throws Exception {
        //查询数据库获取数据
        User user = userService.getUserById(1);
        Assert.assertNotNull(user);
    }
}

整体上注入 userService 服务,编写对应的测试方法,测试方法内部再去调用 userDao 查询数据库的数据,再对返回结果做 Assert 断言检查(这种方式严格来说是属于集成测试,因为涉及到分层)。但是此时 userDao 还没写好,又想单独测试 userService 方法,那么就可以使用 Mockito 模拟假的 userDao 出来,定义其行为方式,这种方式称为stub(存根或者打桩)。

简单理解就是把所需要的测试数据塞到一个对象里,重点关注测试目标的方法,对于不易构造或者不易获取对象和方法都采用桩来代替。

Mockito 提供 @MockBean 注解标注模拟的对象,当加上这个注解之后,Mockito 会帮我们创建一个假的 Mock 对象替换调原有真实的对象,这时候再去注入的 Dao 就已经被替换成 Mock 对象,同时还可以定义具体的参数和返回值,具体用法如下:

Mockito.when(对象.方法名()).thenReturn(自定义结果)

使用 Mockito 之后,上面单元测试的代码就可以变成如下:

@RunWith(SpringRunner.class)
@SpringBootTest
publicclass UserServiceTest {
    
    @Autowired
    private UserService userService;
    
    @MockBean
    private UserDao userDao;

    @Test
    public void getUserById() throws Exception {
        // 定义当调用mock userDao的getUserById()方法,并且参数为3时,就返回id为200、name为I'm mock3的user对象
        Mockito.when(userDao.getUserById(3)).thenReturn(new User(200, "I'm mock 3"));
        // 返回的会是名字为I'm mock 3的user对象
        User user = userService.getUserById(3);
        Assert.assertNotNull(user);
    }
}
 

上面 @mockBean 也可以改成使用 @Mock 注解。

两者的区别主要在于 @Mock 是 Mockito 提供的包;如果不依赖 Spring 容器的测试,那么使用 @mock 就足够了。而 @MockBean 是 spring 提供的注解,需要依赖 Spring 容器,如果需要 mock 被 Spring 管理的 bean,那么就用 @MockBean。

Mockito 除了上面最基本的条件返回自定义结果方法,提供了其他用法。

六、扩展方法

6.1 ThenReturn 系列方法

限制只有指定参数时,才会回传相应数据。例如:当参数的数字是 3 时,才会回传名字为 I’m mock 3 的 对象。

Mockito.when(userService.getUserById(3)).thenReturn(new User(3, "I'm mock"));
// 回传的user的名字为I'm mock
User user1 = userService.getUserById(3); 
// 回传的user为null
User user2 = userService.getUserById(200);

可以指定任何参数,都可以返回数据,比如:使用任何整数值调用 userService 的 getUserById() 方法时,就回传一个名字为 I’m mock3 的 User 对象。

Mockito.when(userService.getUserById(Mockito.anyInt())).thenReturn(new User(3, "I'm mock"));
// 回传的user的名字为I'm mock
User user1 = userService.getUserById(3);
// 回传的user的名字也为I'm mock
User user2 = userService.getUserById(200);

不管传入对象参数是什么,都返回指定的值,比如:任何当调用 userService 的 insertUser() 方法时,都回传 100。

Mockito.when(userService.insertUser(Mockito.any(User.class))).thenReturn(100);
//会返回100
Integer i = userService.insertUser(new User());

6.2 ThenThrow 系列方法

指定调用方法时,跑出异常,例如:当调用 userService 的 getUserById() 时的参数是 9 时,抛出 RuntimeException。

Mockito.when(userService.getUserById(9)).thenThrow(new RuntimeException("mock throw exception"));
// 抛出RuntimeException
User user = userService.getUserById(9);

如果方法本身没有返回值,可以使用 doThrow() 抛出 Exception。

Mockito.doThrow(new RuntimeException("mock throw exception")).when(userService).print();
//抛出一个RuntimeException
userService.print();

6.3 Verify 系列方法

用来检验某个方法的指定参数是否被调用过,这个主要可以用来测试一些异常边界以及页数场景。比如:检查调用 userService 的 getUserById()、且参数为3的次数是否为1次。

Mockito.verify(userService, Mockito.times(1)).getUserById(Mockito.eq(3)) ;

验证调用顺序,校验 userService 是否先调用 getUserById() 两次,并且第一次的参数是 3、第二次的参数是 5,然后才调用 insertUser() 方法。

InOrder inOrder = Mockito.inOrder(userService);
inOrder.verify(userService).getUserById(3);
inOrder.verify(userService).getUserById(5);
inOrder.verify(userService).insertUser(Mockito.any(User.class));

七、总结

单元测试框架 Mockito 很方便的为我们构建模拟环境,方便我们对代码进行测试,单我觉得更多需要的的则是开发人员思维的转变,在原先集成测试的基础上,更细粒度的处理单元测试,再配合相应CodeReview和流程上的规范约束,这样才能较大限度的保证代码质量。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/386954.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Nacos 2.2.0支持postgresql数据库

github地址&#xff1a;个人仓库本文基于扩展源码的方式进行的集成&#xff0c;官方推荐的方式为&#xff1a;扩展插件包源码修改1.1根pom增加postgresql依赖<postgresql.version>42.5.1</postgresql.version><dependency><groupId>org.postgresql<…

C语言数组详解

写在前面 在初识C语言的博客中我们已经知道什么是数组了,并且可以基本的使用,今天我们来详细的谈谈数组是什么,并且实现两个比较好玩的小程序. 数组 数组是什么?C语言中给了数组的定义:一组相同类型元素的集合.我们已经在初始C语言那里已经说过了.我们把下面的一个连续的空…

【全网最细PAT题解】【PAT乙】1049 数列的片段和(思路详细解释)

题目链接 1049 数列的片段和 题目描述 给定一个正数数列&#xff0c;我们可以从中截取任意的连续的几个数&#xff0c;称为片段。例如&#xff0c;给定数列 { 0.1, 0.2, 0.3, 0.4 }&#xff0c;我们有 (0.1) (0.1, 0.2) (0.1, 0.2, 0.3) (0.1, 0.2, 0.3, 0.4) (0.2) (0.2, 0.3)…

首发,pm3包,一个用于多组(3组)倾向评分匹配的R包

目前&#xff0c;本人写的第二个R包pm3包已经正式在CRAN上线&#xff0c;用于3组倾向评分匹配&#xff0c;只能3组不能多也不能少。 可以使用以下代码安装 install.packages("pm3")什么是倾向性评分匹配&#xff1f;倾向评分匹配&#xff08;Propensity Score Match…

MQ-7一氧化碳传感器模块功能实现(STM32)

认识MQ-7模块与其工作原理 首先来认识MQ-7模块&#xff0c;MQ-7可以检测空气中的一氧化碳&#xff08;CO&#xff09;浓度。他采用半导体气敏元件来检测CO的气体浓度&#xff0c;其灵敏度高、反应速度快、响应时间短、成本低廉等特点使得它被广泛应用于智能家居、工业自动化、环…

Leetcode.2373 矩阵中的局部最大值

题目链接 Leetcode.2373 矩阵中的局部最大值 Rating &#xff1a; 1331 题目描述 给你一个大小为 n x n的整数矩阵 grid。 生成一个大小为 (n - 2) x (n - 2)的整数矩阵 maxLocal&#xff0c;并满足&#xff1a; maxLocal[i][j]等于 grid中以 i 1行和 j 1列为中心的 3 x 3…

线段树模板初讲

线段树模板初讲 文章目录线段树模板初讲引入数据结构操作(以求和为例)pushupbuild单点操作&#xff0c;区间查询modifyquery区间操作&#xff0c;区间操作pushdownmodifyquery例题AcWing 1275. 最大数思路代码AcWing 243. 一个简单的整数问题2思路代码总结引入 线段树是算法竞…

systemV共享内存

systemV共享内存 共享内存区是最快的IPC形式。共享内存的大小一般是4KB的整数倍&#xff0c;因为系统分配共享内存是以4KB为单位的&#xff08;Page&#xff09;&#xff01;4KB也是划分内存块的基本单位。 之前学的管道&#xff0c;是通过文件系统来实现让不同的进程看到同一…

通用SQL查询分析器

技术&#xff1a;Java、JSP等摘要&#xff1a;本文主要针对当前很多软件都无法实现跨数据库、跨平台来执行sql语句而用户又仅需做一些基本的增删改查操作的矛盾&#xff0c;设计了一个能够跨平台跨数据库的软件。此软件是一个通用SQL查询分析器&#xff0c;利用java语言本身的跨…

rust中如何利用generic与PhantomData来实现更清晰的接口

前两天看了一个在 rustlang 中如何利用 generic 和 PhantomData 来让我们的 api 更加合理的视频&#xff0c; 当时看完就想写一篇相关内容的文章&#xff0c; 但是没有立即动手&#xff0c;一推迟&#xff0c;不出意外的忘了。这两天又接手了一个半成品的项目&#xff0c; 需要…

C++程序调用IsBadReadPtr或IsBadWritePtr引发内存访问违例问题的排查

目录 1、问题描述 2、VS中看不到有效的信息&#xff0c;尝试使用Windbg去分析 3、使用Windbg分析 4、最后 VC常用功能开发汇总&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&#xff09;https://blog.csdn.net/chenlycly/article/details/12427258…

数据结构-链表-单链表(3)

目录 1. 顺序表的缺陷 2. 单链表 2.1 单链表的基本结构与接口函数 2.2 重要接口 创建新节点的函数&#xff1a; 2.2.1 尾插 2.2.2 头插 2.2.3 尾删 2.2.4 头删 2.2.5 查找 2.2.6 插入 2.2.7 删除 2.2.8 从pos后面插入 2.2.9 从pos后面删除 3. 链表的缺陷与优势&…

传输数据格式:JSON 异步加载

JSON JSON是一种传输数据的格式&#xff08;以对象为样板&#xff0c;本质上就是对象&#xff0c;但用途有区别&#xff0c;对象就是本地用的&#xff0c;json是用来传输的&#xff09;JSON.parse();string --> jsonJSON.stringify();json --> string json ---> {n…

关于安卓的一些残缺笔记

安卓笔记Android应用项目的开发过程Android的调试Android项目文档结构Intent的显式/隐式调用Activity的生命周期1个Activity界面涉及到生命周期的情况2个Activity界面涉及到生命周期的情况Android布局的理论讲解Activity界面布局ContentProvider是如何实现数据共享Android整体架…

mysql视图和存储过程

视图视图就是将一条sql查询语句封装起来&#xff0c;之后使用sql时&#xff0c;只需要查询视图即可&#xff0c;查询视图时会将这条sql语句再次执行一遍。视图不保存数据&#xff0c;数据还是在表中。SELECT 语句所查询的表称为视图的基表&#xff0c;而查询的结果集称为虚拟表…

ATTCK v10版本战术实战研究—持久化(一)

一、前言“在网络安全的世界里&#xff0c;白帽子与黑帽子之间无时无刻都在进行着正与邪的对抗&#xff0c;似乎永无休止。正所谓&#xff0c;道高一尺魔高一丈&#xff0c;巨大的利益驱使着个人或组织利用技术进行不法行为&#xff0c;花样层出不穷&#xff0c;令人防不胜防。…

udk2017环境搭建编译步骤

win10 64bit系统 1.参考minnowboard-max-rel-1-01-bin-releasenotes-for-binary-firmware-images.TXT MyWorkspace.rar 解压到c:\&#xff0c;参考txt中的git操作 3.复制ASL,NASM 到c&#xff1a;\ 安装vs2015 &#xff0c;勾选sdk 5.安装 python-2.7.10.amd64.msi&#xf…

【论文泛读】NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis

NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis | NeRF: 用于视图合成的神经辐射场的场景表示 | 2020年 出自文献&#xff1a;Mildenhall B, Srinivasan P P, Tancik M, et al. Nerf: Representing scenes as neural radiance fields for view synth…

泼辣修图Polarr5.11.4 版,让你的创意无限延伸

泼辣修图是一款非常实用的图片处理软件&#xff0c;它不仅拥有丰富的图片处理功能&#xff0c;而且还能够轻松地实现自定义操作。泼辣修图的操作界面非常简洁&#xff0c;功能也非常丰富&#xff0c;使用起来非常方便快捷。 泼辣修图拥有非常丰富的图片处理功能&#xff0c;包括…

【冲刺蓝桥杯的最后30天】day1

大家好&#x1f603;&#xff0c;我是想要慢慢变得优秀的向阳&#x1f31e;同学&#x1f468;‍&#x1f4bb;&#xff0c;断更了整整一年&#xff0c;又开始恢复CSDN更新&#xff0c;从今天开始逐渐恢复更新状态&#xff0c;正在备战蓝桥杯的小伙伴可以支持一下哦&#xff01;…