学透Spring Boot — 010. 单元测试和Spring Test

news2025/4/5 11:53:49

系列文章目录

这是CSDN postnull 博客《学透Spring Boot》系列的一篇,更多文章请移步:Postnull - 学透Spring Boot系列文章


文章目录

  • 系列文章目录
  • 前言
  • 1. 基本概念
    • UT 单元测试
    • TDD 测试驱动开发
    • UT测试框架
    • Mock框架
  • 3. Spring Test
    • 为什么要用Spring Test
    • 引入Spring Test
    • Spring Test最简单使用


前言

开发的过程中,写业务代码是工作的一部分,测试也是工作的一部分。尤其是作为开发人员,用单元测试来测试我们的代码,可以更早的发现bug。

1. 基本概念

UT 单元测试

单元测试 Unit Testing,通常是开发的一部分,也是开发过程中最小的测试单元。主要测试一个类或一个方法的逻辑。

因为只是保证我们自己写的代码的逻辑,所以是不涉及外部服务的(比如数据库和外部接口),外部服务我们通常使用mock来模拟它们的行为。

UT是代码片段的测试
外部集成测试才需要依赖真正的外部服务

TDD 测试驱动开发

近些年流行很多种 XDD,其中和测试相关的就是TDD,Test driven development,这是一种开发方法。

以前传统的开发模式,我们先一口气写完业务代码,然后再开始写单元测试。但是会遇到一些问题:

  • 业务代码不方便测试
  • 测试时才发现bug,导致业务代码要做很大的调整

所以我们可以换一种思路,先写测试用例,再写业务代码。
这也就是TDD的三个步骤,遵循 红-绿-重构(Red-Green-Refactor)

  1. 红:编写一个失败的测试。
  2. 绿:编写代码使测试通过。
  3. 重构:优化代码,确保测试仍然通过。

我们先快速试一下
先写个测试用例

public class CalculatorTest {
    @Test
    public void testAdd1() {
        Calculator calculator = new Calculator();
        assertEquals(3, calculator.add(1, 2));
    }

    @Test
    public void testAdd2() {
        Calculator calculator = new Calculator();
        assertEquals(0, calculator.add(1, -1));
    }
}

这个时候,测试用例是通不过的,因为我们的业务代码类都还没实现,编译错误。当然这样体验不太好,我们也可以先实现业务代码的骨架,只是空实现。

public class Calculator {
    public Integer add(int a, int b) {
        return null;
    }
}

我们运行一下测试用例,UT不通过,这也是预期的。
在这里插入图片描述

然后我们开始实现代码

public class Calculator {
    public Integer add(int a, int b) {
        return 0;
    }
}

这时候单元用例部分通过了
在这里插入图片描述
说明我们的代码有问题,继续调整

public class Calculator {
    public Integer add(int a, int b) {
        return a + b;
    }
}

这次我们的UT都过了,表示代码基本没问题了。(基本没问题不表示绝对没问题,因为可能测试覆盖率不够,有些边缘的case没有考虑到。)

下次,我们的业务代码被修改了,理论上出了bug,我们的测试用例是要失败的。

public class Calculator {
    public Integer add(int a, int b) {
        if(a < 0 || b < 0){
            return null;
        }
        return a + b;
    }
}

在这里插入图片描述
这时候,我们就需要注意了,是代码有问题,还是测试用例需要更新。

转换思路——测试先行

UT测试框架

Java最常用的 UT 框架有两个

  1. Junit
  2. TestNG

其中Junit比较轻量,满足大部分场景。TestNG功能更强大,比如支持并行测试(同时运行多个测试用例,Junit只能一个完了后再跑下一个)。具体对比:

  • TestNG 的@DataProvider,注入测试数据更方便,Junit5后提供的@ParameteriedTest也差不多功能
  • TestNG支持测试依赖,运行A用例前会自动先触发B用例
  • TestNG支持测试分组,方便运行一组测试用例
  • TestNG支持用例并行执行,对相对独立的case可以大大加快UT的时间
  • ……

TestNG更强大,但有时简单就好

Mock框架

前面也说过,对于外部依赖,甚至是业务代码其它的类,我们都应该模拟,这样保证我们可以关注被测试的这个类和这个方法。
保证测试用例的能够独立、快速的被测试

有很多Mock框架

  • Mockito:最常用
  • PowerMock:兼容Mockito,功能更强大,比如测试静态方法和私有方法(反思:private方法真的应该被测试吗)

比如使用Mockito,步骤基本都是差不多的,先创建mock对象和行为,然后调用方法,最后验证结果

public class UserServiceTest {
    @Test
    public void testGetUser() {
        // 1. 创建 Mock 对象
        UserRepository mockRepo = mock(UserRepository.class);

        // 2. 设置 Mock 行为
        when(mockRepo.findById(1)).thenReturn(new User(1, "John"));

        // 3. 调用测试方法
        UserService userService = new UserService(mockRepo);
        User user = userService.getUser(1);

        // 4. 验证结果
        assertEquals("John", user.getName());
    }
}

3. Spring Test

为什么要用Spring Test

对于Java项目,直接使用TestNG/Junit + Mockito 不就可以了吗?为什么还要用 Spring Test?

以前也有这个疑问,但是如果我们测试Spring 项目时,Junit + Mockito 就有点力不从心了。比如:

  • 注入Spring的配置,比如application-test.xml中配置,注入到Spring容器中去
  • 自动注入Bean,这个做不到,得手动new对象
  • 模拟HTTP请求,这个也做不到,所以Controller测试不了
  • 测试Spring整个应用,也做不到,只能测试单个类

Spring Test就是为了更方便的测试Spring 应用

当然,Spring Test不是要取代 TestNG/Junit + Mockito, 想法,它底层用的还是这些技术。
它只是提供了一堆工具和注解,帮助我们更方便测试Spring应用。

引入Spring Test

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

我们在关联依赖中也可以看到,它自动引入了Junit和Mockito
在这里插入图片描述
这框架本身包含了两个模块

  1. Spring Test 核心模块 spring-boot-test
  2. 自动配置模块 spring-boot-test-autoconfigure: 我们很多自动配置,比如注入
    在这里插入图片描述
    比如在自动配置的模块中,我们可以看到MockMvc的自动配置。这个类我们以后测试会经常遇到。在这里插入图片描述

Spring Test最简单使用

本文我们先来个最简单的例子,测试我们的Controller。

@RestController
@RequestMapping("/tn-users")
public class TnUserController {
    private TnUserService tnUserService;

    public TnUserController(TnUserService tnUserService) {
        this.tnUserService = tnUserService;
    }

    @GetMapping("/{id}")
    public ResponseEntity<TypiUser> getUser(@PathVariable int id) {
        TypiUser user = tnUserService.getUserById(id);
        return user != null ? ResponseEntity.ok(user) : ResponseEntity.notFound().build();
    }
}

然后我们编写测试用例

@WebMvcTest(TnUserController.class)
public class TnUserControllerTest {
    @Autowired
    private MockMvc mockMvc;

    @MockitoBean
    private TnUserService tnUserService;

    @Test
    @DisplayName("测试成功查询用户的情况")
    public void testGetUser() throws Exception {
        //given
        TypiUser mockUser = TypiUser.builder()
                .id(1234)
                .name("Joe")
                .build();

        //when
        when(tnUserService.getUserById(eq(1234))).thenReturn(mockUser);

        //then
        mockMvc.perform(get("/tn-users/{id}", 1234))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.id").value(1234))
                .andExpect(jsonPath("$.name").value("Joe"));
    }
}

测试通过
在这里插入图片描述
稍微改一下代码,再次运行,会发现报错。

        mockMvc.perform(get("/tn-users/{id}", 1234))
                .andExpect(status().isBadRequest());

在这里插入图片描述
可以看到,我们现在具备测试Controller层的能力了。如果没有Spring Test,只是靠Mockito基本做不到接口层的测试。

我们先不用关注实现的细节。下一篇文章我们会全面介绍Spring Test的使用。

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

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

相关文章

TortoiseGit多账号切换配置

前言 之前配置好的都是&#xff0c;TortoiseGit与Gitee之间的提交&#xff0c;突然有需求要在GitHub上提交&#xff0c;于是在参考网上方案和TortoiseGit的帮助手册后&#xff0c;便有了此文。由于GitHub已经配置完成&#xff0c;所以下述以配置Gitee为例。因为之前是单账号使用…

3D 地图渲染-区域纹理图添加

引入-初始化地图&#xff08;关键代码&#xff09; // 初始化页面引入高德 webapi -- index.html 文件 <script src https://webapi.amap.com/maps?v2.0&key您申请的key值></script>// 添加地图容器 <div idcontainer ></div>// 地图初始化应该…

【Linux】条件变量封装类及环形队列的实现

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;博客仓库&#xff1a;https://gitee.com/JohnKingW/linux_test/tree/master/lesson &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &…

离线部署kubesphere(已有k8s和私有harbor的基础上)

前言说明&#xff1a;本文是在已有k8s集群和私有仓库harbor上进行离线安装kubesphere&#xff1b;官网的离线教程写都很详细&#xff0c;但是在部署部份把搭建集群和搭建仓库也写一起了&#xff0c;跟着做踩了点坑&#xff0c;这里就记录下来希望可以帮助到需要的xdm。 1.根据官…

非阻塞IO,fcntl,多路转接,select,poll,epoll,reactor

IO次数会影响程序的效率&#xff0c;在编程中往往会尽量减少IO次数&#xff0c;用以提高程序的效率&#xff0c;例如缓冲区,就是减少IO次数提高效率的一种方式&#xff1b;而IO影响效率的最大原因其实是因为IO等拷贝&#xff0c;在进行IO时往往需要拷贝的数据就绪&#xff0c;或…

Redis常用的数据结构及其使用场景

字符串(String) string 是 redis 最基本的类型&#xff0c;你可以理解成与 Memcached 一模一样的类型&#xff0c;一个 key 对应一个 value。 string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据&#xff0c;比如jpg图片或者序列化的对象。 string 类型是 R…

PhotoShop学习04

1.背景图层 最下面的被锁锁住的图层为背景图层&#xff0c;背景图层充当整个图层的背景&#xff0c;名字标注为背景&#xff0c;无法修改背景图层的排序始终位于图层最底部。 当我想把上方的图层移动到背景图层之后&#xff0c;发现无法移动图层无法移动&#xff0c;把背景图层…

服务器有2张显卡,在别的虚拟环境部署运行了Xinference,然后又建个虚拟环境再部署一个可以吗?

环境: 云服务器Ubuntu系统 2张 NVIDIA H20 96GB Qwen2.5-VL-72B-Instruct-AWQ Qint4量化 AWQ 是 “Activation - Aware Weight Quantization” 的缩写,即激活感知权重量化。它是一种针对大型模型的先进量化算法,通过在权重量化过程中引入对激活值的感知,最小化量化误差…

K8s中CPU和Memory的资源管理

资源类型 在 Kubernetes 中&#xff0c;Pod 作为最小的原子调度单位&#xff0c;所有跟调度和资源管理相关的属性都属于 Pod。其中最常用的资源就是 CPU 和 Memory。 CPU 资源 在 Kubernetes 中&#xff0c;一个 CPU 等于 1 个物理 CPU 核或者一个虚拟核&#xff0c;取决于节…

任务挂起和恢复

任务挂起和恢复API函数 下面用按键和震动传感器验证任务挂起和恢复API函数&#xff1a; PA7接震动传感器&#xff0c;按键引脚为PA0&#xff0c;提前初始化好GPIO引脚 key.c #include "key.h" #include "stm32f10x.h"void KeyInit() {GPIO_InitTypeDef …

【NLP 55、投机采样加速推理】

目录 一、投机采样 二、投机采样改进&#xff1a;美杜莎模型 流程 改进 三、Deepseek的投机采样 流程 Ⅰ、输入文本预处理 Ⅱ、引导模型预测 Ⅲ、候选集筛选&#xff08;可选&#xff09; Ⅳ、主模型验证 Ⅴ、生成输出与循环 骗你的&#xff0c;其实我在意透了 —— 25.4.4 一、…

如何在 Windows 上安装 Python

Python是一种高级编程语言&#xff0c;由于其简单性、多功能性和广泛的应用范围而变得越来越流行。如何在 Windows 操作系统中安装 Python 的过程相对简单&#xff0c;只需几个简单的步骤。 本文旨在指导您完成在 Windows 计算机上下载和安装 Python 的过程。 如何在 Windows…

selectdb修改表副本

如果想修改doris&#xff08;也就是selectdb数据库&#xff09;表的副本数需要首先确定是否分区表&#xff0c;当前没有数据字典得知哪个表是分区的&#xff0c;只能先show partitions看结果 首先&#xff0c;副本数不应该大于be节点数 其次&#xff0c;修改期间最好不要跑业务…

Metabase:一个免费开源的BI平台

今天给大家介绍一个开源数据可视化分析工具&#xff1a;Metabase。它可以帮助用户快速连接数据库、执行查询并创建交互式仪表盘&#xff0c;即使非技术人员也能快速上手。 Metabase 支持多种数据源&#xff0c;包括 MySQL、PostgreSQL、Oracle、SQL Server、SQLite、MongoDB、P…

第15届蓝桥杯省赛python组A,B,C集合

过几天就省赛了&#xff0c;一直以来用的是C&#xff0c;Python蓝桥杯也是刚刚开始准备&#xff08;虽然深度学习用的都是python&#xff0c;但是两者基本没有任何关系&#xff09;&#xff0c;这两天在做去年题时犯了很多低级错误&#xff0c;因此记录一下以便自己复查 PS&am…

为什么有的深度学习训练,有训练集、验证集、测试集3个划分,有的只是划分训练集和测试集?

在机器学习和深度学习中&#xff0c;数据集的划分方式取决于任务需求、数据量以及模型开发流程的严谨性。 1. 三者划分&#xff1a;训练集、验证集、测试集 目的 训练集&#xff08;Training Set&#xff09;&#xff1a;用于模型参数的直接训练。验证集&#xff08;Validati…

虚拟现实 UI 设计:打造沉浸式用户体验

VR UI 设计基础与特点 虚拟现实技术近年来发展迅猛&#xff0c;其独特的沉浸式体验吸引了众多领域的关注与应用。在 VR 环境中&#xff0c;UI 设计扮演着至关重要的角色&#xff0c;它是用户与虚拟世界交互的桥梁。与传统 UI 设计相比&#xff0c;VR UI 设计具有显著的特点。传…

前端Uniapp接入UviewPlus详细教程!!!

相信大家在引入UviewPlusUI时遇到很头疼的问题&#xff0c;那就是明明自己是按照官网教程一步一步的走&#xff0c;为什么到处都是bug呢&#xff1f;今天我一定要把这个让人头疼的问题解决了&#xff01; 1.查看插件市场 重点&#xff1a; 我们打开Dcloud插件市场搜素uviewPl…

【性能优化点滴】odygrd/quill在编译期做了哪些优化

Quill 是一个高性能的 C 日志库&#xff0c;它在编译器层面进行了大量优化以确保极低的运行时开销。以下是 Quill 在编译器优化方面的关键技术和实现细节&#xff1a; 1. 编译时字符串解析与格式校验 Quill 在编译时完成格式字符串的解析和校验&#xff0c;避免运行时开销&…

02 反射 泛型(II)

目录 一、反射 1. 反射引入 2. 创建对象 3. 反射核心用法 二、泛型 1. 泛型的重要性 &#xff08;1&#xff09;解决类型安全问题 &#xff08;2&#xff09;避免重复代码 &#xff08;3&#xff09;提高可读性和维护性 2. 泛型用法 &#xff08;1&#xff09;泛型类 …