Spring Boot 中应用单元测试(UT):结合 Mock 和 H2 讲解和案例示范

1. 引言

单元测试的目标是验证程序中每个模块的正确性。通过编写单元测试,开发者可以确保功能按预期工作,并在未来的开发中减少引入缺陷的风险。Spring Boot 提供了强大的测试支持,结合 Mock 和 H2 数据库,可以高效地进行测试。

2. Spring Boot 单元测试基础

2.1 什么是单元测试?

单元测试是对程序中最小可测试单元(如方法或类)进行验证的过程。它通常由开发者编写,并使用测试框架(如 JUnit、Mockito)来执行。

2.2 Spring Boot 测试支持

Spring Boot 提供了 spring-boot-starter-test 依赖,该依赖包含了测试所需的常用库,如 JUnit、Mockito 和 AssertJ。可以通过以下 Maven 依赖引入:

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

3. 使用 H2 数据库进行测试

H2 数据库是一个轻量级的 Java SQL 数据库,广泛用于单元测试中,尤其是在 Spring Boot 应用中。由于 H2 是内存数据库,它非常适合快速的集成测试和单元测试,因为可以在每次测试前清空数据库,从而确保测试环境的一致性。

3.1 H2 数据库的配置

在 Spring Boot 项目中,配置 H2 数据库通常非常简单。只需在 application.propertiesapplication.yml 文件中添加以下配置:

# application.properties
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

3.2 H2 控制台访问

H2 提供了一个网页控制台,方便开发人员在测试期间查看数据库内容。启用控制台后,访问 http://localhost:8080/h2-console,输入 JDBC URL(如 jdbc:h2:mem:testdb)以及相应的用户名和密码,即可登录。

3.3 在测试中使用 H2 数据库

在编写测试时,可以使用 @DataJpaTest 注解来简化设置,这样 Spring Boot 会自动配置 H2 数据库并扫描 JPA 相关组件。

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.data.jpa.DataJpaTest;
import org.springframework.test.annotation.Rollback;import static org.assertj.core.api.Assertions.assertThat;@DataJpaTest
public class UserActivityRepositoryTest {@Autowiredprivate UserActivityRepository userActivityRepository;@Test@Rollback(false) // 可选,避免测试后清空数据public void testSaveActivity() {UserActivity activity = new UserActivity();activity.setUserId("user1");activity.setAction("login");UserActivity savedActivity = userActivityRepository.save(activity);assertThat(savedActivity.getId()).isNotNull();assertThat(savedActivity.getUserId()).isEqualTo("user1");}
}

3.4 处理 MySQL 函数不兼容的场景

尽管 H2 数据库功能强大,但它并不完全兼容 MySQL 的所有特性。在测试中,如果你使用 MySQL 特有的函数或语法,可能会遇到问题。以下是一些常见的兼容性问题及其解决方案。

3.4.1 使用 H2 特性替代 MySQL 函数

对于 MySQL 中常见的函数,可以查阅 H2 文档,寻找相应的替代函数。例如:

  • MySQL 的 NOW():在 H2 中可以使用 CURRENT_TIMESTAMP
  • MySQL 的 IFNULL(col1, col2):在 H2 中使用 COALESCE(col1, col2)
3.4.2 通过 MODE 配置 MySQL 兼容性

H2 提供了一种方式来设置数据库模式,使其更接近 MySQL 的行为。可以通过以下配置设置 MySQL 模式:

spring.datasource.url=jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE

这种模式使 H2 在处理 SQL 语句时遵循 MySQL 的某些行为。例如,它会识别 MySQL 的 AUTO_INCREMENT 关键字。

3.4.3 自定义 SQL 脚本

如果你的应用依赖于特定的 MySQL 函数或语法,可以通过编写 SQL 脚本,在 H2 数据库中创建所需的视图或存储过程。

CREATE ALIAS IF NOT EXISTS `IFNULL` AS $$
public static String ifnull(String str1, String str2) {return str1 != null ? str1 : str2;
}
$$;

4. Mock 对象的使用

Mock 对象在单元测试中扮演着至关重要的角色。它们用于模拟实际对象的行为,以便我们可以专注于测试特定模块,而无需担心其依赖的外部组件。

4.1 什么是 Mock?

Mock 是对真实对象的模拟,允许开发者定义预期的行为和返回值。通过使用 Mock,开发者可以隔离被测试的单元,从而提高测试的效率和可靠性。

4.2 使用 Mockito 创建 Mock

Mockito 是一个流行的 Java Mock 框架,提供了简单易用的 API。以下是创建和使用 Mock 对象的基本步骤:

  1. 添加依赖:确保在 Maven 中引入 Mockito 依赖(通常已包含在 spring-boot-starter-test 中)。
<dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>3.11.2</version><scope>test</scope>
</dependency>
  1. 创建 Mock 对象:使用 @Mock 注解创建 Mock 对象,并使用 @InjectMocks 注解将其注入到被测试的类中。
@Mock
private UserActivityRepository userActivityRepository;@InjectMocks
private UserActivityService userActivityService;
  1. 初始化 Mock 对象:在测试的 @BeforeEach 方法中使用 MockitoAnnotations.openMocks(this) 来初始化 Mock 对象。
  2. 定义 Mock 行为:使用 when(...).thenReturn(...) 来定义 Mock 对象的行为。例如:
when(userActivityRepository.save(any(UserActivity.class))).thenReturn(activity);
  1. 验证交互:使用 verify(...) 方法来验证 Mock 对象的交互,确保被测试的单元以正确的方式调用了依赖。
verify(userActivityRepository, times(1)).save(activity);

4.3 Mock 对象的优点

  • 解耦:使用 Mock 可以将被测试单元与外部依赖解耦,提高测试的独立性。
  • 可控性:可以控制 Mock 的行为和返回值,以测试不同的场景和边界条件。
  • 简化测试:通过 Mock,可以避免复杂的环境设置和状态管理,简化测试过程。

4.4 示例:使用 Mockito 进行 Mock 测试

下面是一个使用 Mockito 进行 Mock 测试的简单示例:

import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;public class UserServiceTest {@Mockprivate UserActivityRepository userActivityRepository;@InjectMocksprivate UserActivityService userActivityService;@BeforeEachpublic void setUp() {MockitoAnnotations.openMocks(this);}@Testpublic void testSaveActivity() {UserActivity activity = new UserActivity();activity.setId("1");activity.setUserId("user1");activity.setAction("login");when(userActivityRepository.save(activity)).thenReturn(activity);UserActivity savedActivity = userActivityService.saveActivity(activity);assertEquals("user1", savedActivity.getUserId());verify(userActivityRepository, times(1)).save(activity);}
}

5. 数据分析系统案例

在本节中,我们将以一个简单的数据分析系统为案例,展示如何进行单元测试。

5.1 系统需求分析

数据分析系统需要处理用户行为数据,主要功能包括:

  • 存储用户行为记录。
  • 查询用户行为记录。
  • 分析用户活跃度。

5.2 数据模型设计

我们定义一个 UserActivity 类,表示用户行为数据:

import javax.persistence.Entity;
import javax.persistence.Id;
import java.time.LocalDateTime;@Entity
public class UserActivity {@Idprivate String id;private String userId;private String action;private LocalDateTime timestamp;// Getters and Setters
}

5.3 Repository 接口

定义一个 UserActivityRepository 接口用于数据访问:

import org.springframework.data.jpa.repository.JpaRepository;public interface UserActivityRepository extends JpaRepository<UserActivity, String> {List<UserActivity> findByUserId(String userId);
}

5.4 服务层实现

在服务层中实现用户行为数据的存储和查询逻辑:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class UserActivityService {@Autowiredprivate UserActivityRepository userActivityRepository;public UserActivity saveActivity(UserActivity activity) {return userActivityRepository.save(activity);}public List<UserActivity> getActivitiesByUserId(String userId) {return userActivityRepository.findByUserId(userId);}
}

5.5 控制器实现

创建一个 REST 控制器以提供 API 接口:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import java.util.List;@RestController
@RequestMapping("/api/activities")
public class UserActivityController {@Autowiredprivate UserActivityService userActivityService;@PostMappingpublic UserActivity createActivity(@RequestBody UserActivity activity) {return userActivityService.saveActivity(activity);}@GetMapping("/{userId}")public List<UserActivity> getActivities(@PathVariable String userId) {return userActivityService.getActivitiesByUserId(userId);}
}

5.6 单元测试实现

现在,我们开始为上述服务和控制器编写单元测试。

5.6.1 服务层单元测试
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;import java.util.Arrays;
import java.util.List;public class UserActivityServiceTest {@Mockprivate UserActivityRepository userActivityRepository;@InjectMocksprivate UserActivityService userActivityService;@BeforeEachpublic void setUp() {MockitoAnnotations.openMocks(this);}@Testpublic void testSaveActivity() {UserActivity activity = new UserActivity();activity.setId("1");activity.setUserId("user1");activity.setAction("login");when(userActivityRepository.save(activity)).thenReturn(activity);UserActivity savedActivity = userActivityService.saveActivity(activity);assertEquals("user1", savedActivity.getUserId());verify(userActivityRepository, times(1)).save(activity);}@Testpublic void testGetActivitiesByUserId() {UserActivity activity1 = new UserActivity();activity1.setUserId("user1");UserActivity activity2 = new UserActivity();activity2.setUserId("user1");when(userActivityRepository.findByUserId("user1")).thenReturn(Arrays.asList(activity1, activity2));List<UserActivity> activities = userActivityService.getActivitiesByUserId("user1");assertEquals(2, activities.size());verify(userActivityRepository, times(1)).findByUserId("user1");}
}
5.6.2 控制器层单元测试

在控制器层的单元测试中,我们需要确保 REST API 的正确性。通过模拟服务层的行为,我们可以测试控制器对请求的处理是否正确。

控制器层单元测试示例

import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;import java.util.Arrays;public class UserActivityControllerTest {@Autowiredprivate MockMvc mockMvc;@Mockprivate UserActivityService userActivityService;@InjectMocksprivate UserActivityController userActivityController;@BeforeEachpublic void setUp() {MockitoAnnotations.openMocks(this);mockMvc = MockMvcBuilders.standaloneSetup(userActivityController).build();}@Testpublic void testCreateActivity() throws Exception {UserActivity activity = new UserActivity();activity.setId("1");activity.setUserId("user1");activity.setAction("login");when(userActivityService.saveActivity(any(UserActivity.class))).thenReturn(activity);mockMvc.perform(post("/api/activities").contentType(MediaType.APPLICATION_JSON).content("{\"userId\":\"user1\", \"action\":\"login\"}")).andExpect(status().isOk()).andExpect(jsonPath("$.userId").value("user1")).andExpect(jsonPath("$.action").value("login"));}@Testpublic void testGetActivities() throws Exception {UserActivity activity1 = new UserActivity();activity1.setUserId("user1");activity1.setAction("login");when(userActivityService.getActivitiesByUserId("user1")).thenReturn(Arrays.asList(activity1));mockMvc.perform(get("/api/activities/user1")).andExpect(status().isOk()).andExpect(jsonPath("$[0].userId").value("user1")).andExpect(jsonPath("$[0].action").value("login"));}
}

解释

  1. MockitoAnnotations.openMocks(this):初始化 Mock 对象,允许在测试中使用 @Mock 和 @InjectMocks 注解。
  2. MockMvcBuilders.standaloneSetup(userActivityController):创建一个独立的 MockMvc 实例,用于测试控制器。
  3. when(…).thenReturn(…):定义 Mock 对象的行为,当调用特定方法时返回指定的值。
  4. mockMvc.perform(…):模拟 HTTP 请求,并验证返回的状态和内容。

通过上述测试,确保了控制器层对 API 的正确处理,包括创建活动和获取活动的功能。

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

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

相关文章

植物健康,Spring Boot来助力

3系统分析 3.1可行性分析 通过对本植物健康系统实行的目的初步调查和分析&#xff0c;提出可行性方案并对其一一进行论证。我们在这里主要从技术可行性、经济可行性、操作可行性等方面进行分析。 3.1.1技术可行性 本植物健康系统采用SSM框架&#xff0c;JAVA作为开发语言&#…

钉钉录播抓取视频

爬取钉钉视频 免责声明 此脚本仅供学习参考&#xff0c;切勿违法使用下载他人资源进行售卖&#xff0c;本人不但任何责任! 仓库地址: GItee 源码仓库 执行顺序 poxyM3u8开启代理getM3u8url用于获取m3u8文件userAgent随机请求头downVideo|downVideoThreadTqdm单线程下载和…

【纯血鸿蒙】HarmonyOS和OpenHarmony 的区别

一、开源鸿蒙&#xff08;Open Harmony&#xff09; 鸿蒙系统愿来的设计初衷&#xff0c;就是让所有设备都可以运行一个系统&#xff0c;但是每个设备的运算能力和功能都不同&#xff0c;所以内核的设计上&#xff0c;采用了微内核的设计&#xff0c;除了最基础的功能放在内核…

logback 如何将日志输出到文件

如何作 将日志输出到文件需要使用 RollingFileAppender&#xff0c;该 Appender 必须定义 rollingPolicy &#xff0c;另外 rollingPollicy 下必须定义 fileNamePattern 和 encoder <appender name"fileAppender" class"ch.qos.logback.core.rolling.Rollin…

LLM | 论文精读 | 基于大型语言模型的自主代理综述

论文标题&#xff1a;A Survey on Large Language Model based Autonomous Agents 作者&#xff1a;Lei Wang, Chen Ma, Xueyang Feng, 等 期刊&#xff1a;Frontiers of Computer Science, 2024 DOI&#xff1a;10.1007/s11704-024-40231-1 一、引言 自主代理&#xff08;…

企业自建邮件系统选U-Mail ,功能强大、安全稳定

在现代企业运营中&#xff0c;电子邮件扮演着至关重要的角色&#xff0c;随着企业规模的增长和业务的多样化&#xff0c;传统的租用第三方企业邮箱服务逐渐显现出其局限性。例如&#xff0c;存储空间受限、数据安全风险、缺乏灵活的管理和备份功能&#xff0c;以及无法与其他企…

C++从入门到起飞之——红黑树 全方位剖析!

&#x1f308;个人主页&#xff1a;秋风起&#xff0c;再归来~&#x1f525;系列专栏&#xff1a;C从入门到起飞 &#x1f516;克心守己&#xff0c;律己则安 目录 1. 红⿊树的概念 2. 红⿊树的实现 2.1 构建整体框架 2.2 红黑树的插入 2.3 红黑树的验证 2.4 红黑树…

数据库、数据仓库、数据湖和数据中台有什么区别

很多企业在面对数据存储和管理时不知道如何选择合适的方式&#xff0c;数据库、数据仓库、数据湖和数据中台&#xff0c;这些方式都是什么&#xff1f;有什么样的区别&#xff1f;企业根据其业务类型该选择哪一种&#xff1f;本文就针对这些问题&#xff0c;来探讨下这些方式都…

ASP.NET Core 8.0 中使用 Hangfire 调度 API

在这篇博文中&#xff0c;我们将引导您完成将 Hangfire 集成到 ASP.NET Core NET Core 项目中以安排 API 每天运行的步骤。Hangfire 是一个功能强大的库&#xff0c;可简化 .NET 应用程序中的后台作业处理&#xff0c;使其成为调度任务的绝佳选择。继续阅读以了解如何设置 Hang…

山西农业大学20241025

06-VUE 一. 生命周期1. 概念2. 生命周期的钩子函数 二. 工程化开发和脚手架1. 开发vue的两种方式2. 脚手架 Vue CLI3. 使用步骤4 . 项目目录介绍4.1 项目目录4.2 总结 一. 生命周期 1. 概念 VUE生命周期: 就是vue实例从创建到销毁的整个 生命周期经历了四个阶段: ①创建 ②挂载…

Clickhouse 笔记(一) 单机版安装并将clickhouse-server定义成服务

ClickHouse 是一个高性能的列式数据库管理系统&#xff08;DBMS&#xff09;&#xff0c;主要用于在线分析处理&#xff08;OLAP&#xff09;场景。它由俄罗斯搜索引擎公司 Yandex 开发&#xff0c;并在 2016 年开源。ClickHouse 以其卓越的查询性能和灵活的扩展性而闻名&#…

2-133 基于matlab的粒子群算法PSO优化BP神经网络

基于matlab的粒子群算法PSO优化BP神经网络&#xff0c;BP神经网络算法采用梯度下降算法&#xff0c;以输出误差平方最小为目标&#xff0c;采用误差反向传播&#xff0c;训练网络节点权值和偏置值&#xff0c;得到训练模型。BP神经网络的结构(层数、每层节点个数)较复杂时&…

【linux网络编程】| 网络基础 | 解析IP与Mac地址的区别

前言&#xff1a;本节内容讲解一些网络基础相关的知识点&#xff0c; 不涉及网络代码&#xff01;同样的本节内容是作为前一篇的补充知识点&#xff0c; 前一篇文章地址&#xff1a;【linux网络编程】 | 网络基础Ⅰ| 认识网络-CSDN博客&#xff0c;本篇文章内容较少&#xff0c…

AnaTraf | 全面掌握网络健康状态:全流量的分布式网络性能监测系统

AnaTraf 网络性能监控系统NPM | 全流量回溯分析 | 网络故障排除工具AnaTraf网络流量分析仪是一款基于全流量&#xff0c;能够实时监控网络流量和历史流量回溯分析的网络性能监控与诊断系统&#xff08;NPMD&#xff09;。通过对网络各个关键节点的监测&#xff0c;收集网络性能…

内置数据类型、变量名、字符串、数字及其运算、数字的处理、类型转换

内置数据类型 python中的内置数据类型包括&#xff1a;整数、浮点数、布尔类型&#xff08;以大写字母开头&#xff09;、字符串 变量名 命名变量要见名知意&#xff0c;确保变量名称具有描述性和意义&#xff0c;这样可以使得代码更容易维护&#xff0c;使用_可以使得变量名…

2024.7最新子比主题zibll7.9.2开心版源码+授权教程

授权教程&#xff1a; 1.进入宝塔搭建一个站点 绑定 api.zibll.com 域名 并上传 index.php 文件 2.设置伪静态 3.开启SSL证书&#xff0c;找一个能用的域名证书&#xff0c;将密钥(KEY)和证书(PEM格式)复制进去即可 4.在宝塔文件地址栏中输入 /etc 找到 hosts文件并打开&a…

[Linux][进程间通信] 命名管道

命名管道是一种进程间通信的方式,底层原理与匿名管道极为相似,本质是通过在磁盘上新建一个特殊的文件,然后通过这个文件来进行通信 指令: mkfifo [文件名/路径] 该指令用于创建一个命名管道,可以看到文件的类型是p p 类型 命名管道文件 p文件大小恒为0 可通过echo和cat向其…

JavaEE----多线程(四)----阻塞队列的介绍和初步实现

文章目录 1.阻塞队列1.1作用一&#xff1a;解耦合1.2作用二&#xff1a;削峰填谷1.3系统里面的阻塞队列的使用1.4实现普通队列1.5在普通队列的基础上面实现阻塞队列1.6设计优化1.7实现初步的生产者消费者模型 1.阻塞队列 阻塞队列的最大意义&#xff1a;就是实现“生产者消费者…

SQL 干货 | SQL 半连接

大多数数据库开发人员和管理员都熟悉标准的内、外、左和右连接类型。虽然可以使用 ANSI SQL 编写这些连接类型&#xff0c;但还有一些连接类型是基于关系代数运算符的&#xff0c;在 SQL 中没有语法表示。今天我们将学习一种这样的连接类型&#xff1a;半连接&#xff08;Semi …

后台管理员登录实现--系统篇

我的小系统后台原来就有一个上传图片的功能还夹带个删除图片的功能&#xff0c;还嵌到了一个菜单里面。之前效果如下 那么现在为了加大安全力度&#xff0c;想增加一个登录页面。通过登录再到这个页面。看着貌似很简单&#xff0c;但是听我细细说来&#xff0c;要新增些什么东西…