Mockito+junit5搞定单元测试
目录
- 一、简介
- 1.1 单元测试的特点
- 1.2 Mock类框架的使用场景
- 1.3 常见的Mock框架
- 1.3.1 Mockito
- 1.3.2 EasyMock
- 1.3.3 PowerMock
- 1.3.4 Testable
- 1.3.5 比较
- 二、Mockito的使用
- 2.1 导入pom文件
- 2.2 mock对象和spy对象
- 2.3 初始化mock/spy对象的方式
- 2.4 参数匹配
- 2.5 方法插桩
- 2.6 @InjectMocks注解的使用
- 2.7 断言工具
一、简介
1.1 单元测试的特点
- 配合断言使用(杜绝System.out)
- 可重复执行
- 不依赖环境
- 不会对数据产生影响
- Spring的上下文环境不是必须得
- 一般都需要配合Mock类框架来实现的
1.2 Mock类框架的使用场景
要进行测试的方法存在外部依赖(如数据库,Redis,第三方接口调用等),为了能够专注对该方法或者单元的逻辑进行测试,就希望能够虚拟出外部依赖,避免外部依赖成为测试的阻塞项。
1.3 常见的Mock框架
Mock类框架:用于Mock外部依赖。
1.3.1 Mockito
官网:http://mockito.org/
官网文档:https://www.javadoc.io/doc/org.mockito/mockito-core/4.5.1/org/mockito/Mockito.html#13
限制:老版本对于final class、final method、statis method、private method均不能对Mockito mock,目前新版本已经支持final class、final method、statis method方法的mock,具体可以参考官网(有空了再补)
1.3.2 EasyMock
1.3.3 PowerMock
文档:https://github.com/powermock/powermock/wiki/Getting-Started
PowerMock
是一款功能十分强大的Mock工具,其基本语法与Mockito
兼容,同时扩展了许多Mockito
缺失的功能,包括对支持对私有、静态和构造方法实施Mock。但由于使用了自定义类加载器,会导致Jacoco在默认的on-the-fly
模式下覆盖率跌零。
powerMock是基于easyMock或Mockito扩展出来的增强版本,所以powerMock分两种类型,如果你习惯于使用easyMock的,那你就下载基于easyMock的powerMock,反之你喜欢用mockito的话就下载另一种PowerMock。
但是好像也没有多少人用。。。
1.3.4 Testable
文档:https://alibaba.github.io/testable-mock/#/
TestableMock
现在已不仅是一款轻量易上手的单元测试Mock工具,更是以简化Java单元测试为目标的综合辅助工具集,与PowerMock
基本平齐,且极易上手,只需掌握一个@MockInvoke
注解就可以完成绝大多数Mock操作。
1.3.5 比较
工具 | 原理 | 最小Mock单元 | 对被Mock方法的限制 | 上手难度 | IDE支持 |
---|---|---|---|---|---|
Mockito | 动态代理 | 类 | 不能Mock私有方法 | 较容易 | 很好 |
PowerMock | 自定义类加载器 | 类 | 任何方法皆可 | 较复杂 | 较好 |
JMockit | 运行时字节码修改 | 类 | 不能Mock构造方法(new操作符) | 较复杂 | 一般 |
TestableMock | 运行时字节码修改 | 方法 | 任何方法皆可 | 较容易 | 较好 |
二、Mockito的使用
2.1 导入pom文件
导入Mockito坐标和junit5的坐标
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.7.0</version><scope>compile</scope></dependency><dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>3.6.28</version><scope>compile</scope></dependency>
导入Mockito坐标和junit5的坐标,前期工作已经完成。
如果在springboot中我们还可以直接引用下面的坐标即可,其中的依赖已经包含了上述两者。
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>3.0.13</version><scope>test</scope>
</dependency>
2.2 mock对象和spy对象
方法类型 | 方法插桩 | 方法不插桩 | 作用对象 | 最佳实践 |
---|---|---|---|---|
mock对象 | 执行插桩逻辑 | 返回mock对象的默认值 | 类、接口 | 被测试类或者其他依赖 |
spy对象 | 执行插桩逻辑 | 调用真实方法 | 类、接口 | 被测试类 |
- Mock不是真实的对象,它只是用类型的class创建了一个虚拟对象,并可以设置对象行为
- Spy是一个真实的对象,但它可以设置对象行为
2.3 初始化mock/spy对象的方式
测试版本 | 方法一 | 方法二 | 方法三 |
---|---|---|---|
junit4 | @RunWith(MockitoJUnitRunner.class) + @Mock等注解 | MockitoAnnotations.initMocks(this); | Mockito.mock(x.class) |
junit5 | @ExtendWith(MockitoExtension.class)+ @Mock等注解 | MockitoAnnotations.initMocks(this); | Mockito.mock(x.class) |
MockitoAnnotations.initMocks(this)方法已经被openMocks(this)替代。
我们现在来介绍一下初始化的三种方式:现阶段先关注初始化方法就行。。
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;import org.junit.jupiter.api.BeforeEach;
import org.mockito.MockitoAnnotations;import org.mockito.Mockito;/*** 初始化mock/spy对象的第一种方式*/
@ExtendWith(MockitoExtension.class)
public class TeacherServiceTestMethod1 {@Mockprivate TeacherService teacherService;@Spyprivate UserService userService;@Testpublic void test1(){//Mockito.mockingDetails(teacherService).isMock() 用来判断该对象是不是一个mock的对象System.out.println(Mockito.mockingDetails(teacherService).isMock());System.out.println(Mockito.mockingDetails(userService).isSpy());System.out.println();}
}/*** 初始化mock/spy对象的第二种方式*/
class TeacherServiceTestMethod2 {@Mockprivate TeacherService teacherService;@Spyprivate UserService userService;@BeforeEachvoid setUp() {//MockitoAnnotations.initMocks(this); 该方法已过时MockitoAnnotations.openMocks(this);}@Testpublic void test1(){System.out.println(Mockito.mockingDetails(teacherService).isMock());System.out.println(Mockito.mockingDetails(userService).isSpy());System.out.println();}
}/*** 初始化mock/spy对象的第三种方式*/
class TeacherServiceTestMethod3 {private TeacherService teacherService;private UserService userService;@BeforeEachvoid setUp() {userService = Mockito.mock(UserService.class);teacherService = Mockito.spy(TeacherService.class);}@Testpublic void test1(){System.out.println(Mockito.mockingDetails(teacherService).isMock());System.out.println(Mockito.mockingDetails(userService).isSpy());System.out.println();}
}
我们随意找一个Test方法debug一下只要看到对象是这样就ok了。注意$MockitoMock
就是说明模拟成功了
com.surpass.service.UserService$MockitoMock$1070386111@55e8ec2f
2.4 参数匹配
将参数匹配和方法插桩一起示例。。。
2.5 方法插桩
指定调用某个方法时的行为(stubbing),达到相互隔离的目的。
- 返回指定值
- void返回值方法插桩
- 插桩的两种方式
- doXxx().when(obj).method(); 其中obj可以使mock/spy对象
- when(obj.method()).thenXxx();其中obj可以使mock对象
- 抛异常
- 多次插桩
- thenAnswer
- 执行真正的原始方法
- verify的使用
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;/*** 参数匹配:通过方法签名(参数)来指定哪些方法调用时需要被处理(插桩、verify验证)* 注意:在只用匹配器是要么都用要么都不用,禁止混搭!!举例* 正确:getUserListByTeacher("name", "address")或者getUserListByTeacher(anyString(), anyString())* 错误:getUserListByTeacher(anyString(), "address")*/
@ExtendWith(MockitoExtension.class)
public class ParamMatcherTest {@Mockprivate TeacherService teacherService;/*** 对于mock对象不会调用真实方法,直接返回mock对象的默认值* 默认值(int)、null、空集合*/@Testpublic void test1(){TeacherEntity teacher = teacherService.getTeacherByName("");System.out.println("teacher = " + teacher);Set<UserEntity> userListByTeacherName = teacherService.getUserListByTeacherName("");System.out.println("userListByTeacherName = " + userListByTeacherName);}/*** 方法插桩时的参数匹配* 参数匹配时类(ArgumentMatchers)是匹配参数的主要成员,例如:* any()则表示某一个方法传入任何类型都符合要求* anyString()则表示字符串类型都符合要求* !!!注意:所有的匹配都不包括null值*/@Testpublic void test2(){TeacherEntity teacherEntity = new TeacherEntity();teacherEntity.setName("张三");teacherEntity.setAddress("北京");//when(teacherService.getTeacherByName("")).thenReturn(teacherEntity); 此行插桩方式也可,此插桩意为当执行getTeacherByName方法是会返回之前创建好的对象teacherEntitydoReturn(teacherEntity).when(teacherService).getTeacherByName(any());TeacherEntity teacher = teacherService.getTeacherByName("123");System.out.println("teacher = " + teacher);//验证校验teacherService.getTeacherByName()调用的次数,之前调了一次所以校验通过,如两次则抛异常verify(teacherService, times(1)).getTeacherByName(any());}
}控制台将打印如下内容
teacher = TeacherEntity(id=null, name=张三, address=北京, age=0)
其他项目的解释
@Mockprivate List<String> mockList;/*** 被插桩的方法在调用时不会执行实际的逻辑,直接返回指定的返回值*/@Testpublic void test3(){/*** 指定返回值*///方法插桩:当调用mockList.get(0)时返回指定返回值“zero”doReturn("zero").when(mockList).get(0);Assertions.assertEquals("zero", mockList.get(0));when(mockList.get(1)).thenReturn("one");Assertions.assertEquals("one", mockList.get(1));/*** void返回值方法插桩*/doNothing().when(mockList).clear();mockList.clear();verify(mockList, times(1)).clear();/*** 抛异常*/doThrow(RuntimeException.class).when(mockList).clear();try {mockList.clear();//断言证明插桩失败Assertions.fail();} catch (Exception e) {Assertions.assertTrue(e instanceof RuntimeException);}//或when(mockList.get(anyInt())).thenThrow(RuntimeException.class);try {mockList.get(4);//断言证明插桩失败Assertions.fail();} catch (Exception e) {Assertions.assertTrue(e instanceof RuntimeException);}/*** 多次插桩* 意为第一次调用返回1,第二次调用返回2,第三次以及以后调用返回3*/when(mockList.size()).thenReturn(1).thenReturn(2).thenReturn(3);//或-----两者相同when(mockList.size()).thenReturn(1, 2, 3);Assertions.assertEquals(1, mockList.size());Assertions.assertEquals(2, mockList.size());Assertions.assertEquals(3, mockList.size());Assertions.assertEquals(3, mockList.size());}/*** thenAnswer来实现对指定逻辑的插桩*/@Testpublic void test4() {when(mockList.get(anyInt())).thenAnswer((Answer<String>) invocation -> {//getArgument表示获取插桩方法(此处为mockList.get(anyInt()))的第几个参数值Integer argument = invocation.getArgument(0, Integer.class);return String.valueOf(argument * 100);});//执行get方法System.out.println(mockList.get(1));}/*** 调用真实逻辑:控制台打印* 进入方法: getTeacher* TeacherEntity(id=1, name=123, address=北京市, age=-1166257546)*/@Testpublic void test5() {when(teacherService.getTeacher(any())).thenCallRealMethod();System.out.println(teacherService.getTeacher("123"));}
2.6 @InjectMocks注解的使用
- 作用:若此注解声明的变量需要用的mock/spy对象,mockito会自动将当前类里面的mock/spy对象注入到当中
- 原理:构造器注入、setter注入、字段反射注入
TeacherService实现 toString()
方法,调用 System.out.println(teacherService);
控制台打印如下内容:
TeacherService{teacherDao=teacherDao, userDao=userDao}
dubug能够看到两个Dao被自动注入到Service中去。
@ExtendWith(MockitoExtension.class)
public class InjectMocksTest {/*** 被InjectMocks注解标注的助兴必须是实现类,因为mockito会创建对应的实例对象* 未经过mockito处理的普通对象会配合@spy注解使其变成默认调用真实方法的mock对象* mockito会使用spy对象或mock对象注入到InjectMocks对应的实例对象中*/@Spy@InjectMocksprivate TeacherService teacherService;@Mockprivate TeacherDao teacherDao;@Mockprivate UserDao userDao;@Testpublic void test1(){System.out.println(teacherService);}
}
2.7 断言工具
- hamcrest:junit4中引入的第三方断言库,junit5中被移除掉了。
- assertj:常用断言库。
- junit4原生断言库
- junit5原生断言库
junit5常用断言类库:org.junit.jupiter.api.Assertions
。
相关文章:
Mockito+junit5搞定单元测试
目录 一、简介1.1 单元测试的特点1.2 Mock类框架的使用场景1.3 常见的Mock框架1.3.1 Mockito1.3.2 EasyMock1.3.3 PowerMock1.3.4 Testable1.3.5 比较 二、Mockito的使用2.1 导入pom文件2.2 mock对象和spy对象2.3 初始化mock/spy对象的方式2.4 参数匹配2.5 方法插桩2.6 InjectM…...
PostgreSQL获取当天、昨天、本月、上个月、本年、去年的数据
gps_time为timestamp类型日期字段 获取当天的数据 WHERE DATE_TRUNC(day, gps_time) CURRENT_DATE --或 WHERE DATE(gps_time) CURRENT_DATE获取昨天的数据 WHERE DATE_TRUNC(day, gps_time) CURRENT_DATE - INTERVAL 1 day获取本月的数据 WHERE DATE_TRUNC(month, gps_…...

XCTF:stage1[WriteUP]
从题目中下载到图片: 考虑图片是png,隐写方式有可能是高宽修改,也可能是色相隐藏,色彩通道位隐藏等等 使用stegsolve对图片进行一下伽马、颜色转换 在图片的左上角就显示出了一个二维码 使用QR_Rresearch工具对二维码扫描 获得一…...

STM32CubeMX教程13 ADC - 单通道转换
目录 1、准备材料 2、实验目标 3、ADC概述 4、实验流程 4.0、前提知识 4.1、CubeMX相关配置 4.1.1、时钟树配置 4.1.2、外设参数配置 4.1.3、外设中断配置 4.2、生成代码 4.2.1、外设初始化调用流程 4.2.2、外设中断调用流程 4.2.3、添加其他必要代码 5、常用函数…...

矩阵的乘法
首先矩阵的乘法定义如下: #include <stdio.h> int main() { int i 0; int j 0; int arr[20][20] { 0 }; int str[20][20] { 0 }; int s[20][20] { 0 }; int n1 0; int n2 0; int m2 0; int z 0; int m1 0;…...
python爬取招聘网站数据
这段代码是使用Selenium自动化测试模块进行网页爬取的示例代码。它通过模拟人的行为在浏览器中操作网页来实现爬取。具体的流程如下: 导入所需的模块,包括Selenium、时间、随机、csv等模块。打开浏览器,创建一个Chrome浏览器实例。设置要爬取…...

灌区信息化方案(什么是现代化灌区,如何一步到位)
一、系统概述 详情:https://www.key-iot.com.cn/ 本灌区信息化方案以星创易联公司的各类智能设备为基础,通过其产品完成水文、雨情、土壤等多源异构数据的采集,以无线自组网的方式实现数据传输,并在后台管理中心建立信息化软件平台,对数据进行融合处理。系统实现对…...

jmeter自动录制脚本功能
问题排查: 建议用 google浏览器; 重启一下jmeter; 过滤规则重新检查下; 看下代理设置是否正常; 注意:下面的的过滤设置中 用的都是正则表达式的规则。...

十一、工具盒类(MyQQ)(Qt5 GUI系列)
目录 编辑 一、设计需求 二、实现代码 三、代码解析 四、总结 一、设计需求 抽屉效果是软件界面设计中的一种常用形式,可以以一种动态直观的方式在有限大小的界面上扩展出更多的功能。本例要求实现类似 QQ 抽屉效果。 二、实现代码 #include "dialog.…...
postgresql 查询字段 信息
SELECT base.“column_name”, col_description ( t1.oid, t2.attnum ), base.udt_name, COALESCE(character_maximum_length, numeric_precision, datetime_precision), (CASE WHEN ( SELECT t2.attnum ANY ( conkey ) FROM pg_constraint WHERE conrelid t1.oid AND contyp…...

antv/x6_2.0学习使用(四、边)
一、添加边 节点和边都有共同的基类 Cell,除了从 Cell 继承属性外,还支持以下选项。 属性名类型默认值描述sourceTerminalData-源节点或起始点targetTerminalData-目标节点或目标点verticesPoint.PointLike[]-路径点routerRouterData-路由connectorCon…...
C++ stack用法总结
std::stack 是 C 标准模板库(STL)中的容器适配器,它提供了栈(stack)的功能,基于其他序列容器实现。以下是 std::stack 的用法总结: 包含头文件: #include <stack>创建 std::…...

【大数据进阶第三阶段之Datax学习笔记】阿里云开源离线同步工具Datax概述
【大数据进阶第三阶段之Datax学习笔记】阿里云开源离线同步工具Datax概述 【大数据进阶第三阶段之Datax学习笔记】阿里云开源离线同步工具Datax快速入门 【大数据进阶第三阶段之Datax学习笔记】阿里云开源离线同步工具Datax类图 【大数据进阶第三阶段之Datax学习笔记】使用…...

PHP 基础编程 2
文章目录 时间函数dategetdatetime 使用数组实现登录注册和修改密码简单数组增加元素方法修改元素方法删除元素方法 具体实现方法数组序列化数组写入文件判断元素是否在关联数组中(登录功能实现)实现注册功能实现修改admin用户密码功能 时间函数 时区&am…...
git merge origin master 和 git merge origin/master 的区别
git merge origin master和git merge origin/master的区别 1. git checkout dev 2. git fetch origin master 3. git merge origin release 把 origin/master,heads/release merge到 heads/dev1. git checkout dev 2. git fetch origin master 3. git me…...
数据挖掘 模糊聚类
格式化之前的代码: import matplotlib.pyplot as plt#绘图 import pandas as pd#读取数据集 from sklearn.preprocessing import scale from sklearn.cluster import DBSCAN#聚类 from sklearn import preprocessing#数据预处理的功能,包括缩放、标准化…...
Vue2和Vue3各自的优缺点以及区别对比
Vue2和Vue3各自的优缺点以及区别对比 Vue2的优点: 成熟稳定:Vue2是一个经过长时间发展和测试的成熟版本,广泛应用于各种项目中。 生态系统丰富:由于Vue2的流行程度,它的生态系统相对较为完善,有大量的插件…...

手写一个加盐加密算法(java实现)
目录 前言 什么是MD5?? 加盐算法 那别的人会不会跟你得到相同的UUID? 如何使用盐加密? 代码实现 前言 对于我们常见的登录的时候需要用到的组件,加密是一个必不可少的东西,如果我们往数据库存放用户…...

基于Springboot的在线考试系统
点击以下链接获取源码: https://download.csdn.net/download/qq_64505944/88499371 mysql5、mysql8都可使用 内含配置教程文档,一步一步配置 Springboot所写 管理员页面 学生页面...

【React系列】JSX核心语法和原理
本文来自#React系列教程:https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5MDAzNzkwNA&actiongetalbum&album_id1566025152667107329) 一. ES6 的 class 虽然目前React开发模式中更加流行hooks,但是依然有很多的项目依然是使用类组件&#x…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

使用分级同态加密防御梯度泄漏
抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...

全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...

苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...

微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...

Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...