当前位置: 首页 > news >正文

Junit + Mockito保姆级集成测试实践

一、做好单测,慢即是快

对于单元测试的看法,业界同仁理解多有不同,尤其是在业务变化快速的互联网行业,通常的问题主要有,必须要做吗?做到多少合适?现在没做不也挺好的吗?甚至一些大佬们也是存在不同的看法。我们如下先看一组数字:

“在 STICKYMINDS 网站上的一篇名为 《 The Shift-Left Approach to Software Testing 》 的文章中提到,假如在编码阶段发现的缺陷只需要 1 分钟就能解决,那么单元测试阶段需要 4 分钟,功能测试阶段需要 10 分钟,系统测试阶段需要 40 分钟,而到了发布之后可能就需要 640 分钟来修复。”——来自知乎网站节选

对于这些数字的准确性我们暂且持保留意见。大家可以想想我们实际中遇到的线上问题大概需要消耗多少工时,除了要快速找到bug,修复bug上线,还要修复因为bug引发的数据问题,最后还要复盘,看后续如何能避免线上问题,这样下来保守估计应该不止几人日吧。所以这篇文章作者所做的调研数据可信度还是很高的,

缺陷发现越到交付流程的后端,其修复成本就越高。

有人说写单测太耗费时间了,会延长交付时间,其实不然:

1)研测同学大量的往返交互比编写单测的时间要长的多,集成测试的时间被拖长。

2)没经过单测的代码bug会多,开发同学忙于修复各种bug,对代码debug跟踪调试找问题,也要消耗很多精力。

3)后期的线上问题也会需要大量的精力去弥补。

如果有了单元测试的代码,且能实现一个较高的行覆盖率,则可以将问题尽可能消灭在开发阶段。同时有了单测代码的积累,每次代码改动后可以提前发现这次改动引发的其他关联问题,上线也更加放心。单测虽然使提测变慢了一些,软件质量更加有保障,从而节省了后续同学的精力,从整体看其实效率更高。

所以做好单测,慢即是快。

做为一名开发者我们需要对自己的代码质量负责,也更能体现我们开发者的工匠精神。

二、编写单元测试

Junit5使用

maven依赖

<!-- Springboot提供的单测框架,提供一些单测工具支持,默认支持Mockito、junit5 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><version>2.5.4</version>
</dependency><!-- 或单独引入 -->
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.7.2</version><scope>compile</scope>
</dependency>
<dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>3.9.0</version><scope>compile</scope>
</dependency>
<dependency><groupId>org.mockito</groupId><artifactId>mockito-junit-jupiter</artifactId><version>3.9.0</version><scope>compile</scope>
</dependency>

Juint常用注解

单类示例

通过idea的Squaretest插件直接生成的测试类如下

@ExtendWith(MockitoExtension.class)
public class MockUserServiceTest {@Mockprivate UserManager mockUserManager;@InjectMocksprivate MockUserService mockUserService;@BeforeEachpublic void setUp() {mockUserService = new MockUserService(mockUserManager);}@Testpublic void testGetUserByAge() {// Setupwhen(mockUserManager.findByAge(0)).thenReturn(Arrays.asList(new User(0, "name", 0),new User(1, "name", 0)));// Run the testfinal List<User> result = mockUserService.getUserByAge(0);// Verify the results}@Testpublic void testGetUserByAge_UserManagerReturnsNoItems() {// Setupwhen(mockUserManager.findByAge(0)).thenReturn(Arrays.asList(new User(0, "name", 0), new User(1, "name", 1)));// Run the testfinal List<User> result = mockUserService.getUserByAge(0);// Verify the resultsassertThat(result).isEqualTo(Collections.emptyList());}
}

需注意Junit5.x 与Junit4.x 生成的测试类中,Junit4的测试类和测试方法必须要public关键字修改

因为JUnit 4.x使用Java反射机制来查找和运行测试,而Java反射要求被访问的类和方法必须是public的。

JUnit 5.x(也称为Jupiter)在设计和实现上更加现代化,它引入了一些新的特性和改进,包括更灵活的测试发现机制。在JUnit 5.x中,测试类和测试方法的访问修饰符要求更加宽松。

将测试方法和类声明为public也有助于确保它们能够被其他测试框架或工具(如Maven、Gradle、IDE等)正确地发现和运行。因此,在编写JUnit测试时,即使JUnit 5.x允许更宽松的访问修饰符,但将测试类和测试方法声明为public仍然是一个好习惯

springboot集成测试

springboot集成测试旨在验证Spring Boot应用程序的各个组件之间的交互和整体行为。集成测试非常重要,因为它可以帮助开发人员确保应用程序在不同的环境中都能正常运行。通过集成测试,可以检测应用程序中的潜在问题,提高代码的可靠性和稳定性。

Mockito常用注解

@MockBean: 用于在 Spring Boot 测试环境中创建并注入一个 mock 的 bean。
用途:用于在 Spring Boot 测试环境中创建一个 mock 的 bean,并将其注入到 Spring 应用程序上下文中。
特点:
适用于集成测试,特别是在使用 @SpringBootTest 注解的测试类中。
替换掉 Spring 容器中已有的 bean,或者添加一个新的 mock bean。
可以在测试类中直接使用 @Autowired 注解来注入这个 mock bean。


@Mock: 用于创建一个 mock 对象,但不将其注入到 Spring 应用程序上下文中。
用途:用于创建一个 mock 对象,但不将其注入到 Spring 应用程序上下文中。
特点:
适用于单元测试,特别是在不需要 Spring 上下文的测试中。
需要手动注入到测试类或方法中。
通常与 @InjectMocks 一起使用,以便将 mock 对象注入到被测试的类中。


@Spy: 用于创建一个部分 mock 对象,即一个真实的对象,但可以对其中的某些方法进行 mock。
用途:用于创建一个部分 mock 对象,即一个真实的对象,但可以对其中的某些方法进行 mock。
特点:
适用于需要调用真实对象的方法,同时对某些方法进行 mock 的场景。
可以使用 doReturn(...).when(...) 或 when(...).thenReturn(...) 来模拟方法的行为。


@InjectMocks: 用于创建一个被测试的类的实例,并将带有 @Mock 或 @Spy 注解的 mock 对象注入到该实例中。
用途:用于创建一个被测试的类的实例,并将带有 @Mock 或 @Spy 注解的 mock 对象注入到该实例中。
特点:
适用于需要将 mock 对象注入到被测试的类中的场景。
自动将 mock 对象注入到被测试类的构造函数、字段或 setter 方法中。

集成示例

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MockInjectServiceImplTest{    /*** 通过@MockBean的方式创建一个Mock的MockRpcService的Bean* 并将其注入到spring的上下文中*/@MockBeanprivate MockRpcService mockRpcService;@Resourceprivate MockInjectService mockInjectService;@BeforeEachvoid setUp() {MockitoAnnotations.openMocks(this);ReflectionTestUtils.setField(mockInjectService, "systemEnv", "{\"key\", \"value\"}");when(mockRpcService.queryCardNo(anyString())).thenReturn("cardNo");/*** 1、when(...).thenReturn(...) 语法:* 这种语法在某些情况下可能会被 Mockito 误认为是在调用 toString 方法,特别是当 mockRpcService 对象的 toString 方法被重写时。* 若在使用 Mockito 模拟这个接口时遇到了 WrongTypeOfReturnValue 异常,这通常意味着 Mockito 误认为你在调用 toString 方法而不是 queryMockResp 方法* 如果 mockRpcService 的 toString 方法返回 MockResp 类型,那么 Mockito 会抛出 WrongTypeOfReturnValue 异常。** 2、doReturn(...).when(...) 语法:* 这种语法更加明确,直接指定了方法的返回值,避免了类型不匹配的问题。适用于所有需要模拟方法返回值的场景。* 为了确保代码的健壮性和可读性,建议使用 doReturn(...).when(...) 语法。**** 下面的例子 使用when(...).thenReturn(...)时 抛出了org.mockito.exceptions.misusing.WrongTypeOfReturnValue:* MockResp cannot be returned by toString() toString() should return String* 这样的异常。*///when(mockRpcService.queryMockResp(any(MockReq.class))).thenReturn(MockRespReflection.getMockResp());doReturn(MockRespReflection.getMockResp()).when(mockRpcService).queryMockResp(any(MockReq.class));doReturn(MockRespReflection.getMockRespList()).when(mockRpcService).getMockRespList(anyInt());}@Testpublic void testGeneralDeal(){// 执行被测方法MockReq mockReqInput1 = new MockReq();mockReqInput1.setName("True-Person");MockResp mockRespResult = mockInjectService.generalDeal(mockReqInput1);log.info("mockResp:{}", JSON.toJSONString(mockRespResult));// 结果比对断言Assert.assertNotNull(mockRespResult);}@Testpublic void testInjectDeal() {// 执行被测方法MockReq mockReqInput1 = new MockReq();mockReqInput1.setName("True-Person");MockResp mockRespResult = mockInjectService.injectDeal(mockReqInput1);// 结果比对断言Assert.assertNotNull(mockRespResult);}@Testpublic void testBeautifulDeal() {// Setupfinal MockResp mockResp = new MockResp("cardNo", 0, false);// Run the testfinal String result = mockInjectService.beautifulDeal(mockResp);// Verify the resultsassertThat(result).isEqualTo("result");}@Testpublic void testVoidDeal() {// Setupfinal MockReq req = new MockReq();req.setName("name");// Run the testmockInjectService.voidDeal(req);}
}

以上示例,通过@MockBean创建一个Rpc服务MockRpcService的mock实例,可以对接口的相关方法通过when(...).thenReturn(...) 或doReturn(...).when(...)语法mock。

需注意when(...).thenReturn(...)语法在某些情况下可能会被 Mockito 误认为是在调用 toString 方法,特别是当 mockRpcService 对象的 toString 方法被重写时

而doReturn(...).when(...) 语法更加明确,直接指定了方法的返回值,避免了类型不匹配的问题。适用于所有需要模拟方法返回值的场景。建议使用 doReturn(...).when(...) 语法

RPC接口MockRpcService

*** Mockito框架研发场景-RPC接口*/
public interface MockRpcService {String queryCardNo(String name);MockResp queryMockResp(MockReq req);public List<MockResp> getMockRespList(Integer age);

通过MockRespReflection类中的静态方法 对RPC接口的方法数据进行mock,可以采用直接字符串、文件等形式提前准备数据,这里采用读取文件形式进行mock

ublic class MockRespReflection {public static MockResp getMockResp() {try {String json = new String(Files.readAllBytes(Paths.get("src/test/file/xxx.json")));return JSON.parseObject(json, new TypeReference<MockResp>(){});} catch (IOException e) {throw new RuntimeException(e);}}/*** 从指定的JSON文件中读取并解析MockResp对象列表** @return 解析后的MockResp对象列表* @throws RuntimeException 如果读取文件时发生IO异常,将其包装成RuntimeException抛出*/public static List<MockResp> getMockRespList() {try {// 读取JSON文件内容并解析为MockResp对象列表String json = new String(Files.readAllBytes(Paths.get("src/test/file/mockRespList.json")));return JSON.parseObject(json, new TypeReference<List<MockResp>>(){});} catch (IOException e) {// 捕获IO异常并将其包装成RuntimeException抛出throw new RuntimeException(e);}}
}

通过以上配置就可以进行springboot流程的集成测试。Spring Boot集成测试是确保应用程序正确性和可靠性的重要手段。通过上述实践,可以有效地进行集成测试并提高代码质量。

参考:

一台不容错过的Java单元测试代码“永动机”-CSDN博客

相关文章:

Junit + Mockito保姆级集成测试实践

一、做好单测&#xff0c;慢即是快 对于单元测试的看法&#xff0c;业界同仁理解多有不同&#xff0c;尤其是在业务变化快速的互联网行业&#xff0c;通常的问题主要有&#xff0c;必须要做吗&#xff1f;做到多少合适&#xff1f;现在没做不也挺好的吗&#xff1f;甚至一些大…...

软件项目管理要点

一.项目管理 1.盈亏平衡分析 销售额固定成本可变成本税费利润 当利润为0的时候就是盈亏平衡点。 2.范围管理 范围定义的输入包括&#xff1a;项目章程、项目范围管理计划、组织过程资产、批准的变更申请。 3.时间管理 项目时间管理中的过程包括活动定义、活动排序、活动的资…...

ESP8266 连接 MQTT 服务器EMQX 连接MQTTX

目录 1.先用有一台自己的云服务器 2. 使用FinalShell连接阿里云云服务器ECS 3.安装宝塔 4.在云服务器打开8888端口 5.使用外网面板地址打开宝塔面板 6.安装Docker 7.下载emqx 8.打开emqxWeb 界面 9.下载MQTTX 10.EMQX加一个客户端 11.开始通信 12.加入单片机ESP8266 …...

Python中如何处理异常情况?

1、Python中如何处理异常情况&#xff1f; 在Python中&#xff0c;处理异常情况通常使用try/except语句。try语句块包含可能会引发异常的代码&#xff0c;而except语句块包含处理异常的代码。如果try块中的代码引发了异常&#xff0c;控制流将立即转到相应的except块。 以下是…...

openpnp - 在openpnp中单独测试相机

文章目录 openpnp - 在openpnp中单独测试相机概述笔记END openpnp - 在openpnp中单独测试相机 概述 底部相机的位置不合适, 重新做了零件&#xff0c;准备先确定一下相机和吸嘴的距离是多少才合适。 如果在设备上直接实验&#xff0c;那么拆装调整相机挺麻烦的。 准备直接在电…...

Spark窗口函数

1、 Spark中的窗口函数 窗口就是单纯在行后面加一个列 可以套多个窗口函数&#xff0c;但彼此之间不能相互引用&#xff0c;是独立的 窗口函数会产生shuffle over就是用来划分窗口的 (1) 分组聚合里面的函数&#xff0c;基…...

Idea、VS Code 如何安装Fitten Code插件使用

博主主页:【南鸢1.0】 本文专栏&#xff1a;JAVA 目录 ​编辑 简介 所用工具 1、Idea如何安装插件 1.idea下载插件 2.需要从外部下载然后在安装&#xff0c; 2、VS Code如何安装插件 总结 简介 Fitten Code是由非十大模型驱动的AI编程助手&#xff0c;它可以自动生成代…...

elasticsearch7.x在k8s中的部署

一、说明 二、思路 三、部署 1、建nfs服务器 2、建持久卷 3、部署elasticsearch 四、附件 ?pv.yaml内容 elasticsearch.yaml内容 一、说明 本文章内容主要的参考来源是https://www.cnblogs.com/javashop-docs/p/12410845.html&#xff0c;但参考文献中的elasticsearc…...

校园社团信息管理平台:Spring Boot技术实战指南

3系统分析 3.1可行性分析 通过对本校园社团信息管理系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本校园社团信息管理系统采用SSM框架&#xff0c;JAVA作…...

【Linux】从内核角度理解 TCP 的 全连接队列(以及什么是 TCP 抓包)

文章目录 概念引入理解全连接队列内核方面理解Tcp抓包方法注意事项 概念引入 我们知道&#xff0c;TCP的三次握手是由TCP协议 自动处理的&#xff0c;建立连接的过程与用户是否进行accept无关&#xff0c;accept()的作用主要是为当前连接创建一个套接字&#xff0c;用于进行后…...

太速科技-712-6U VPX飞腾处理器刀片计算机

6U VPX飞腾处理器刀片计算机 一、产品概述 该产品是一款基于国产飞腾FT-2000四核处理器或D2000八核处理器的高性能6U VPX刀片式计算机。产品提供了可支持全网状交换的高速数据通道&#xff0c;其中P1、P2均支持1个PCIe x16 Gen3或2个PCIe x8 Gen3或4个PCIe x4 Gen3总…...

深度学习(八) TensorFlow、PyTorch、Keras框架大比拼(8/10)

一、深度学习框架概述 深度学习框架在当今人工智能和机器学习领域中占据着至关重要的地位。其中&#xff0c;TensorFlow 由 Google 开发&#xff0c;自 2015 年发布以来&#xff0c;凭借其灵活的计算图、自动微分功能以及跨平台支持等特点&#xff0c;迅速成为主流深度学习框架…...

thinkphp中命令行工具think使用,可用于快速生成控制器,模型,中间件等

在使用tp框架的时候tp内置了一个think的命令行工具&#xff0c; 这个工具可以帮助我们快速的生成控制器&#xff0c;模型&#xff0c;中间件的代码。 这个工具就和laravel中的 artisan 的作用都差不多&#xff0c;只是名称不同而已。 tp中常用的命令行工具&#xff1a; 1. 快…...

Discourse 是否支持手机注册

Discourse 能不能使用手机进行注册&#xff1f; 功能总结 简答来说&#xff0c;Discourse 不能使用手机注册&#xff0c;Discourse 也没有提供这个功能。 这个功能多是需要 SSO 来实现。 比如说华为的那个社区&#xff0c;他们自己做了一个自己的用户管理 SSO&#xff0c;然…...

软件测试学习笔记丨Flask框架-请求与响应

本文转自测试人社区&#xff0c;原文链接&#xff1a;https://ceshiren.com/t/topic/23408 请求方法 from flask import Flaskapp Flask(__name__)app.route("/cases", methods["get"]) def get_case():return {"code": 0, "msg": &…...

【C++笔记】list结构剖析及其模拟实现

【C笔记】list结构剖析及其模拟实现 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;C笔记 文章目录 【C笔记】list结构剖析及其模拟实现前言一 .list的结构及其介绍1.1list的结构1.2list的使用1.3迭代器划分 二.list的模拟实现2.1 list结构…...

C#进阶1

C#进阶1 本文章主要介绍C#的进阶知识&#xff0c;如反射&#xff0c;特性.... 参考视频链接 原码 文章目录 C#进阶1反射步骤泛型反射调用方法 获取属性 特性特性的定义步骤扩展枚举练习 反射 在 C# 中&#xff0c;反射&#xff08;Reflection&#xff09;是一种强大的机制&a…...

PHP如何对输出进行转义

在PHP中&#xff0c;对输出进行转义是为了防止跨站脚本攻击&#xff08;XSS&#xff09;和其他安全问题。PHP提供了多种函数来对输出进行转义&#xff0c;这些函数根据输出的上下文&#xff08;如HTML、JavaScript、URL等&#xff09;而有所不同。以下是一些常用的转义函数及其…...

Windows 10 安装Docker踩过的坑和解决-31/10/2024

目录 环境版本 一、Docker Desktop双击启动没反应&#xff0c;open //./pipe/dockerDesktopLinuxEngine: The system cannot find the file specified. 二、Docker Desktop运行run命令时显示错误HTTP code 500 并且错误大意是服务器拒绝访问 三、检测Docker是否可以正常使用…...

【应急响应】Linux植入恶意程序排查流程

文章目录 前言一、Linux入侵检查二、Linux系统被入侵/中毒有哪些现象三、Linux系统被入侵/中毒处置过程四、Linux安全防护措施五、服务器被GetShell渗透解决办法(案例)前言 本篇文章主要是以我们日常的运维工作中对Linux服务器进行安全检查,进一步介绍如何使用具体命令来对Li…...

R语言AI模型部署方案:精准离线运行详解

R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂

蛋白质结合剂&#xff08;如抗体、抑制肽&#xff09;在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上&#xff0c;高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术&#xff0c;但这类方法普遍面临资源消耗巨大、研发周期冗长…...

macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用

文章目录 问题现象问题原因解决办法 问题现象 macOS启动台&#xff08;Launchpad&#xff09;多出来了&#xff1a;Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显&#xff0c;都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

Python实现prophet 理论及参数优化

文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候&#xff0c;写过一篇简单实现&#xff0c;后期随着对该模型的深入研究&#xff0c;本次记录涉及到prophet 的公式以及参数调优&#xff0c;从公式可以更直观…...

力扣-35.搜索插入位置

题目描述 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...

搭建DNS域名解析服务器(正向解析资源文件)

正向解析资源文件 1&#xff09;准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2&#xff09;服务端安装软件&#xff1a;bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...

【JVM】Java虚拟机(二)——垃圾回收

目录 一、如何判断对象可以回收 &#xff08;一&#xff09;引用计数法 &#xff08;二&#xff09;可达性分析算法 二、垃圾回收算法 &#xff08;一&#xff09;标记清除 &#xff08;二&#xff09;标记整理 &#xff08;三&#xff09;复制 &#xff08;四&#xff…...

Leetcode33( 搜索旋转排序数组)

题目表述 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转&#xff0c;使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nu…...

java高级——高阶函数、如何定义一个函数式接口类似stream流的filter

java高级——高阶函数、stream流 前情提要文章介绍一、函数伊始1.1 合格的函数1.2 有形的函数2. 函数对象2.1 函数对象——行为参数化2.2 函数对象——延迟执行 二、 函数编程语法1. 函数对象表现形式1.1 Lambda表达式1.2 方法引用&#xff08;Math::max&#xff09; 2 函数接口…...

C++11 constexpr和字面类型:从入门到精通

文章目录 引言一、constexpr的基本概念与使用1.1 constexpr的定义与作用1.2 constexpr变量1.3 constexpr函数1.4 constexpr在类构造函数中的应用1.5 constexpr的优势 二、字面类型的基本概念与使用2.1 字面类型的定义与作用2.2 字面类型的应用场景2.2.1 常量定义2.2.2 模板参数…...