Spring Boot框架下的单元测试

1. 什么是单元测试

1.1 基本定义

  • 单元测试(Unit Test) 是对软件开发中最小可测单位(例如一个方法或者一个类)进行验证的一种测试方式。
  • 在 Java 后端的 Spring Boot 项目中,单元测试通常会借助 JUnitMockito 等框架对代码中核心逻辑进行快速且隔离的验证,保证功能正确性。

目的:及早发现并修复 BUG,使后续迭代功能或重构时能迅速验证不会破坏已实现的功能。

1.2 单元测试在 Spring Boot 中的地位

  • Spring Boot 提供了非常方便的测试支持,如 @SpringBootTest@TestConfiguration 等注解,让开发者可以快速地在带有 Spring 容器上下文的环境中执行测试。
  • Spring Boot 本身也对 JUnit、Mockito、AssertJ 等常用测试框架或库提供了开箱即用的整合或依赖。

1.3 单元测试与其他测试的区别

  • 单元测试:聚焦在一个方法或者一个类层面,不涉及过多外部依赖,能极快地发现逻辑错误。
  • 集成测试:多个模块或组件交互时的测试,通常依赖真实数据库、消息队列等外部资源。
  • 端到端测试(E2E):关注的是整个系统的完整流程,包括前端、后端、数据库、外部接口等。
  • 在 Spring Boot 环境中,可以使用 @SpringBootTest 搭配 Mock 或者内存数据库来实现集成测试,但这通常已经不只是“单元”级别了。

2. 为什么要写单元测试?

  • 快速发现 Bug:写完代码马上测,不用等到上线才被发现问题。
  • 减少回归成本:以后代码改动或升级,只要一键跑测试,就能知道改动有没有影响其他功能。
  • 保证代码质量:养成单元测试的习惯,会促使你把代码设计得更简洁和更容易测试。

简单说:花小时间写单元测试,能为你省下大时间修 Bug。


3. 环境准备

3.1 依赖

在一个常规的 Spring Boot 项目中,只要在 pom.xml(Maven)或 build.gradle(Gradle) 里加上:

<!-- 如果是 Maven -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
  • JUnit 5:最常用的Java测试框架(写 @Test 方法)
  • Mockito:常用的“模拟”库(用来Mock其他依赖)
  • AssertJ / Hamcrest:更好用的断言库
  • Spring Test / Spring Boot Test:Spring官方提供的测试辅助

这也就够了,一般不需要额外安装别的。

3.2 项目结构

Spring Boot常见的目录结构(Maven示例):

src
 ├─ main
 │   └─ java
 │       └─ com.example.demo
 │           ├─ DemoApplication.java
 │           └─ service
 │               └─ MyService.java
 └─ test
     └─ java
         └─ com.example.demo
             ├─ DemoApplicationTests.java
             └─ service
                 └─ MyServiceTest.java
  • src/main/java 放你的业务代码
  • src/test/java 放你的测试代码
  • 通常测试类的包路径要和被测类一致,这样在IDE里能很快对上号,也方便管理。

4. 最最简单的单元测试示例(不依赖Spring)

先从“纯JUnit”说起,最简单的情况就是:

  • 我有一个普通的工具类/方法
  • 我就想测试它的输入输出对不对
  • 不用装载Spring,也不用什么复杂注解

代码示例

假设我们有一个简单的工具类:

public class MathUtil {
    public static int add(int a, int b) {
        return a + b;
    }
}

那我们写一个测试类(路径:src/test/java/.../MathUtilTest.java):

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

public class MathUtilTest {

    @Test
    void testAdd() {
        int result = MathUtil.add(2, 3);
        Assertions.assertEquals(5, result, "2 + 3 应该等于 5");
    }
}
  • @Test 表示这是一个测试方法。
  • Assertions.assertEquals(期望值, 实际值, "提示信息") 用来断言。
    • 如果断言不通过,测试就失败;通过则测试成功。

运行方法:

  • 在 IDE(如 IntelliJ/ Eclipse)里,右键这个 MathUtilTest 类 -> Run 'MathUtilTest'
  • 或者在命令行里运行 mvn test(Maven) / gradle test(Gradle)。

这就是最最基础的单元测试


5. 在 Spring Boot 里测试 - Service层

当你要测试一个 Service(业务逻辑类) 时,它可能依赖其他Bean(例如 Repository、Dao 等)或者需要 Autowired。在 Spring Boot 里,有两种主要方法:

方法1:纯Mock(不启动Spring Context)

适合只想测试这个Service逻辑本身,不需要真的连数据库,也不需要整个Spring环境。速度最快。

  • 用 Mockito 来创建一个假的(Mock)依赖。
  • 注入到要测的Service里,这样你可以控制依赖的行为。

示例

UserRepository.java (假设它是个接口,用来访问数据库):

public interface UserRepository {
    User findByName(String name);
    // ... 其他方法
}

UserService.java (我们要测这个类):

public class UserService {

    private UserRepository userRepository;

    // 通过构造注入依赖
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public String getUserNickname(String name) {
        User user = userRepository.findByName(name);
        if (user == null) {
            return "UNKNOWN";
        }
        return user.getNickname();
    }
}

UserServiceTest.java (测试类,不依赖 Spring):

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Assertions;
import org.mockito.Mockito;
import org.mockito.Mock;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(MockitoExtension.class) // JUnit5 启用Mockito
public class UserServiceTest {

    @Mock
    private UserRepository userRepository; // Mock出来的依赖

    @InjectMocks
    private UserService userService;       // 要测试的对象,会把上面这个Mock自动注入进来

    @Test
    void testGetUserNickname_found() {
        // 1. 假设我们模拟一个“数据库中查到的用户”:
        User mockUser = new User();
        mockUser.setName("alice");
        mockUser.setNickname("AliceWonder");

        // 2. 定义假数据的返回行为
        Mockito.when(userRepository.findByName("alice")).thenReturn(mockUser);

        // 3. 调用被测方法
        String nickname = userService.getUserNickname("alice");

        // 4. 断言结果
        Assertions.assertEquals("AliceWonder", nickname);
    }

    @Test
    void testGetUserNickname_notFound() {
        // 没有设置when,则默认返回null
        String nickname = userService.getUserNickname("bob");
        Assertions.assertEquals("UNKNOWN", nickname);
    }
}
  • 使用了 @Mock 注解声明要模拟的依赖 userRepository
  • 使用了 @InjectMocks 注解告诉 Mockito,要把所有标记 @Mock 的对象注入进 UserService
  • 这样就能让 UserService 这个对象在执行时使用模拟过的 userRepository 而不访问真实数据库。
  • 然后通过 Mockito.when(...) 来定义依赖方法的返回值,用于测试用例的前提条件设置。
  • 通过 Assertions 来验证执行结果是否符合预期。

这样就只测 UserService 的逻辑,不会真的访问数据库,也不需要启动Spring,执行很快。

方法2:使用 @SpringBootTest (集成上下文)

适合你想在测试时使用Spring管理Bean,比如自动注入 @Autowired,或想测试和别的Bean的连接配置是否正常。

  • 在测试类上加 @SpringBootTest
  • 这样Spring容器会启动,你也能 @Autowired 你的Service或者别的Bean。

示例

UserService.java (类似前面,只不过换成了 Spring注入):

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    public String getUserNickname(String name) {
        User user = userRepository.findByName(name);
        if (user == null) {
            return "UNKNOWN";
        }
        return user.getNickname();
    }
}

UserServiceSpringTest.java (测试类,使用Spring上下文):

@SpringBootTest
public class UserServiceSpringTest {

    @Autowired
    private UserService userService;

    @MockBean
    private UserRepository userRepository; 
    // @MockBean的意思:Spring 启动时,
    // 把真正的UserRepository替换成一个Mock对象,
    // 我们就可以定义它的返回值,而不会真的连数据库

    @Test
    void testGetUserNickname_found() {
        User mockUser = new User();
        mockUser.setName("alice");
        mockUser.setNickname("AliceWonder");

        Mockito.when(userRepository.findByName("alice")).thenReturn(mockUser);

        String result = userService.getUserNickname("alice");
        Assertions.assertEquals("AliceWonder", result);
    }

    @Test
    void testGetUserNickname_notFound() {
        // 不设置when就会返回null
        String result = userService.getUserNickname("unknown");
        Assertions.assertEquals("UNKNOWN", result);
    }
}
  • @SpringBootTest会启动一个小型Spring环境,让 @Autowired 能起作用。
  • @MockBean 可以让你把某个Bean(比如 UserRepository)变成一个模拟对象。
  • 整体执行依然比较快,但比纯Mock稍微慢一点,因为要先启动Spring容器。

6. 测试 Controller 层

在 Spring Boot 里,Controller 是对外的 HTTP 接口。最常见的两种测试方式:

  • @WebMvcTest + MockMvc:不启动整个应用,只启动Web层,速度较快;
  • @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) + TestRestTemplate:会真正启动一个内嵌服务器,发起真实HTTP请求,更贴近实际环境。

6.1 @WebMvcTest 示例

@WebMvcTest(UserController.class) // 表示只测 UserController 相关
public class UserControllerTest {

    @Autowired
    private MockMvc mockMvc; // 用来模拟HTTP请求

    @MockBean
    private UserService userService; // Mock掉Service层

    @Test
    void testGetUser() throws Exception {
        // 假设Service返回一个User对象
        User mockUser = new User();
        mockUser.setName("test");
        mockUser.setNickname("TestNick");

        // 定义service行为
        Mockito.when(userService.getUserNickname("test")).thenReturn("TestNick");

        // 用MockMvc发起GET请求,对应Controller的 /user/{name} 路径
        mockMvc.perform(MockMvcRequestBuilders.get("/user/test"))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andExpect(MockMvcResultMatchers.content().string("TestNick"));
    }
}
  • @WebMvcTest 只会扫描和加载 Web 层相关的组件,不会启动整个 Spring Boot 应用,测试速度更快。
  • mockMvc.perform(get("/users/1")) 可以模拟一次 GET 请求到 /users/1,并断言返回的 JSON 结构和内容。

6.2 @SpringBootTest + TestRestTemplate

如果你想做一个更真实的集成测试(包括 Controller、Service、Repository 等所有层),可以使用 @SpringBootTest 并设置 webEnvironment = RANDOM_PORTDEFINED_PORT 来启动内嵌服务器,然后注入 TestRestTemplate 来请求: 

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class UserControllerIntegrationTest {

    @Autowired
    private TestRestTemplate restTemplate; // 可以真的发请求

    @Test
    void testGetUser() {
        // 假设数据库里已经有对应数据,或者你用 @MockBean 替换依赖
        String result = restTemplate.getForObject("/user/test", String.class);
        Assertions.assertEquals("TestNick", result);
    }
}
  • 这里会真正启动一个随机端口的Tomcat,然后 TestRestTemplate 真的去请求本地这个 /user/test 接口。
  • 非常贴近真实部署,只是适合做集成测试,比前面的MockMvc测试稍慢一点。

7. 常见的断言与技巧

7.1 断言

  • Assertions.assertEquals(期望, 实际):断言二者相等。
  • Assertions.assertTrue(条件):断言条件为真。
  • Assertions.assertThrows(异常类型, 代码块):断言执行代码块会抛出指定异常。

例如:

@Test
void testThrowException() {
    Assertions.assertThrows(IllegalArgumentException.class, () -> {
        // 假设调用了一个会抛出异常的方法
        someMethod(null);
    });
}

7.2 Mock时常用的 Mockito 方法

  • Mockito.when( mockObj.方法(...) ).thenReturn(返回值);
  • Mockito.when( mockObj.方法(...) ).thenThrow(异常);
  • Mockito.verify( mockObj, Mockito.times(1) ).某方法(...); // 验证是否调用了某方法

8. 测试运行与整合

8.1 在本地IDE里运行

  • 右键单个测试类或测试方法 -> Run
  • 或者在项目主目录运行 mvn test / gradle test

8.2 与持续集成(CI)整合

  • 在 Jenkins、GitLab CI、GitHub Actions 等环境里,一般只要执行 mvn testgradle test 就可以跑所有测试用例。
  • 如果测试全部通过,就说明代码基本没问题;如果测试挂了,说明你这次提交的改动有Bug或者破坏了原有逻辑。

9. 流程小结(简版“使用指南”)

  • 新手首次写单元测试

    • src/test/java 下创建和源代码同包路径的测试类:XXXTest.java
    • 在类里加 @Test 注解的方法,里面写 Assertions.assertXXX(...)
    • 右键运行,看输出是否通过。
  • 要测Service逻辑,但不想连数据库

    • 在测试类上写:
      @ExtendWith(MockitoExtension.class)
      public class MyServiceTest {
          @Mock
          private MyRepository myRepository;
      
          @InjectMocks
          private MyService myService;
          ...
      }
      
    • Mockito.when(...) 来模拟依赖。
    • assertEquals(...) 来判断结果。
  • 要测Service逻辑,并用Spring上下文

    • 在测试类上加 @SpringBootTest
    • 注入 Service:@Autowired private MyService myService;
    • 如果你不想真的连数据库,那就用 @MockBean MyRepository myRepository;
  • 要测Controller

    • @WebMvcTest(MyController.class) + @MockBean MyService myService; + MockMvc 做单元测试,速度较快;
    • 或者用 @SpringBootTest(webEnvironment = ... ) + TestRestTemplate 做近似真实的集成测试。

10. 其他常见问题

  • 测试和生产环境的配置冲突了怎么办?
    • 可以在 application-test.yml 里放测试专用配置,然后在测试时用 spring.profiles.active=test
  • 需要数据库的测试怎么办?
    • 可以用@DataJpaTest+内存数据库(比如 H2),只测JPA相关逻辑,不影响真数据库。
  • 想看覆盖率怎么办?
    • 可以集成 Jacoco 插件,跑 mvn test 后生成覆盖率报告,看你的测试是不是覆盖到了主要逻辑。
  • 测试很慢怎么办?
    • 如果你的逻辑不是必须要Spring,就尽量用纯Mock,不用 @SpringBootTest
    • 如果只是测Controller,就用 @WebMvcTest,不要启动全部。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【前端】ES6模块化

文章目录 1. 模块化概述1.1 什么是模块化?1.2 为什么需要模块化? 2. 有哪些模块化规范3. CommonJs3.1 导出数据3.2 导入数据3.3 扩展理解3.4 在浏览器端运行 4.ES6模块化4.1 浏览器运行4.2 在node服务端运行4.3 导出4.3.1 分别导出4.3.2 统一导出4.3.3 默认导出4.3.4 混用 4.…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】2.16 记录数组:面向对象的数据操作

2.16 记录数组&#xff1a;面向对象的数据操作 内容提要 本文将深入探讨 NumPy 的 recarray 数据结构&#xff0c;这是一种特殊的数据类型&#xff0c;允许用户以面向对象的方式访问数组中的数据。我们首先介绍 recarray 的基本特性&#xff0c;然后讨论如何优化属性访问&…

本地搭建deepseek-r1

一、下载ollama(官网下载比较慢&#xff0c;可以找个网盘资源下) 二、安装ollama 三、打开cmd&#xff0c;拉取模型deepseek-r1:14b(根据显存大小选择模型大小&#xff09; ollama pull deepseek-r1:14b 四、运行模型 ollama run deepseek-r1:14b 五、使用网页api访问&#x…

linux本地部署deepseek-R1模型

国产开源大模型追平甚至超越了CloseAI的o1模型&#xff0c;大国崛起时刻&#xff01;&#xff01;&#xff01; DeepSeek R1 本地部署指南   在人工智能技术飞速发展的今天&#xff0c;本地部署AI模型成为越来越多开发者和企业关注的焦点。本文将详细介绍如何在本地部署DeepS…

手写MVVM框架-环境搭建

项目使用 webpack 进行进行构建&#xff0c;初始化步骤如下: 1.创建npm项目执行npm init 一直下一步就行 2.安装webpack、webpack-cli、webpack-dev-server&#xff0c;html-webpack-plugin npm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin 3.配置webpac…

git基础使用--4---git分支和使用

文章目录 git基础使用--4---git分支和使用1. 按顺序看2. 什么是分支3. 分支的基本操作4. 分支的基本操作4.1 查看分支4.2 创建分支4.3 切换分支4.4 合并冲突 git基础使用–4—git分支和使用 1. 按顺序看 -git基础使用–1–版本控制的基本概念 -git基础使用–2–gti的基本概念…

想品客老师的第十天:类

类是一个优化js面向对象的工具 类的声明 //1、class User{}console.log(typeof User)//function//2、let Hdclass{}//其实跟1差不多class Stu{show(){}//注意这里不用加逗号&#xff0c;对象才加逗号get(){console.log(后盾人)}}let hdnew Stu()hd.get()//后盾人 类的原理 类…

JavaFX - 3D 形状

在前面的章节中&#xff0c;我们已经了解了如何在 JavaFX 应用程序中的 XY 平面上绘制 2D 形状。除了这些 2D 形状之外&#xff0c;我们还可以使用 JavaFX 绘制其他几个 3D 形状。 通常&#xff0c;3D 形状是可以在 XYZ 平面上绘制的几何图形。它们由两个或多个维度定义&#…

arm-linux-gnueabihf安装

Linaro Releases windows下打开wsl2中的ubuntu&#xff0c;资源管理器中输入&#xff1a; \\wsl$gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz 复制到/home/ark01/tool 在 Ubuntu 中创建目录&#xff1a; /usr/local/arm&#xff0c;命令如下&#xff1a; …

【双指针题目】

双指针 美丽区间&#xff08;滑动窗口&#xff09;合并数列&#xff08;双指针的应用&#xff09;等腰三角形全部所有的子序列 美丽区间&#xff08;滑动窗口&#xff09; 美丽区间 滑动窗口模板&#xff1a; int left 0, right 0;while (right < nums.size()) {// 增大…

【汽车电子软件架构】AutoSAR从放弃到入门专栏导读

本文是汽车电子软件架构&#xff1a;AutoSAR从放弃到入门专栏的导读篇。文章延续专栏文章的一贯作风&#xff0c;从概念与定义入手&#xff0c;希望读者能对AutoSAR架构有一个整体的认识&#xff0c;然后对专栏涉及的文章进行分类与链接。本文首先从AutoSAR汽车软件架构的概念&…

八、Spring Boot 日志详解

目录 一、日志的用途 二、日志使用 2.1 打印日志 2.1.1 在程序中获取日志对象 2.1.2 使用日志对象打印日志 2.2、日志框架介绍 2.2.1 门面模式(外观模式) 2.2.2 门面模式的实现 2.2.3 SLF4J 框架介绍 2.3 日志格式的说明 2.4 日志级别 2.4.1 日志级别的分类 2.4.2…

【Linux】24.进程信号(1)

文章目录 1. 信号入门1.1 进程与信号的相关知识1.2 技术应用角度的信号1.3 注意1.4 信号概念1.5 信号处理常见方式概览 2. 产生信号2.1 通过终端按键产生信号2.2 调用系统函数向进程发信号2.3 由软件条件产生信号2.4 硬件异常产生信号2.5 信号保存 3. 阻塞信号3.1 信号其他相关…

[Proteus仿真]基于51单片机的智能温控系统

[Proteus仿真]基于51单片机的智能温控系统 基于51单片机的智能温控系统&#xff1a;DS18B20精准测温LCD1602双屏显示三键设置上下限声光报警&#xff0c;支持温度校准、抗干扰设计、阈值记忆。 一.仿真原理图 ​​ 二.模块介绍 温度采集模块&#xff08;DS18B20&#xff0…

Windows下怎么安装FFFmpeg呢?

在Windows下使用Open-webui报错&#xff0c;说Couldnt find ffmpeg or avconv,解决open-webui报错Couldn‘t find ffmpeg or avconv-CSDN博客于是尝试解决问题&#xff0c;那么Windows下怎么安装FFFmpeg呢&#xff1f; 尝试了两种方法。 第一种方法pip安装&#xff08;失败&…

C基础寒假练习(2)

一、输出3-100以内的完美数&#xff0c;(完美数&#xff1a;因子和(因子不包含自身)数本身 #include <stdio.h>// 函数声明 int isPerfectNumber(int num);int main() {printf("3-100以内的完美数有:\n");for (int i 3; i < 100; i){if (isPerfectNumber…

【智力测试——二分、前缀和、乘法逆元、组合计数】

题目 代码 #include <bits/stdc.h> using namespace std; using ll long long; const int mod 1e9 7; const int N 1e5 10; int r[N], c[N], f[2 * N]; int nr[N], nc[N], nn, nm; int cntr[N], cntc[N]; int n, m, t;void init(int n) {f[0] f[1] 1;for (int i …

Vue-el挂载点

目录 一、Vue中的el挂载点是什么&#xff1f;二、Vue实例的作用范围是什么呢&#xff1f;三、Vue中的el是否可以挂载哪些选择器&#xff1f;四、el是否可以设置其他的dom元素呢&#xff1f; 一、Vue中的el挂载点是什么&#xff1f; el是用来设置Vue实例挂载&#xff08;管理&a…

c语言练习【实现终端功能、dup2实现文件拷贝、read write文件加载到链表】

练习1&#xff1a;实现终端功能 请实现一个终端的功能&#xff0c;注意需要带有cd功能 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h>#define MAX_CM…

MySQL数据库环境搭建

下载MySQL 官网&#xff1a;https://downloads.mysql.com/archives/installer/ 下载社区版就行了。 安装流程 看b站大佬的视频吧&#xff1a;https://www.bilibili.com/video/BV12q4y1477i/?spm_id_from333.337.search-card.all.click&vd_source37dfd298d2133f3e1f3e3c…