单元测试实战(三)JPA 的测试
为鼓励单元测试,特分门别类示例各种组件的测试代码并进行解说,供开发人员参考。
本文中的测试均基于JUnit5。
单元测试实战(一)Controller 的测试
单元测试实战(二)Service 的测试
单元测试实战(三)JPA 的测试
单元测试实战(四)MyBatis-Plus 的测试
单元测试实战(五)普通类的测试
单元测试实战(六)其它
我们常用的DAO层开发框架包括JPA(Spring Boot Data Jpa)和MyBatis-Plus。
框架都提供根据一定规约自动生成查询/更新的操作,如代理接口。这一部分责任在框架本身,我们不需测试。因此DAO的测试应主要针对自定义查询/更新操作。
JPA的测试注解是@DataJpaTest,Mybatis-Plus的是@MyBatisPlusTest;它们的思想是一致的,此次先讲JPA。
概述
JPA组件表现为Repository对象。如果是Spring Data JPA标准的Repository,且使用接口代理,那么理论上是不需要测试的;但实际中不排除想验证订制代码(native SQL、JPQL、标准查询API,以及胶水代码)的需求。
JPA测试有专门的@DataJpaTest注解,是Spring boot测试框架提供的功能,因此它是有Spring上下文的,使用JUnit的SpringExtension扩展类。
测试还是遵循经典三段式:given、when、then;即:假设xxx……那么当yyy时……应该会zzz。
在每个测试之前应清理/重置测试数据,即操作的数据实体。
断言应主要检查数据存取行为是否符合预期。
依赖
JPA测试除了依赖JUnit和Spring boot测试框架外还依赖一个内存数据库,我们用H2,如下:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency>
<dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><scope>test</scope>
</dependency>
<dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>test</scope>
</dependency>
示例
以下是用代理接口实现的UserRepository:
package com.aaa.api.auth.repository;import org.springframework.data.jpa.repository.JpaRepository;
import com.aaa.api.auth.entity.User;
import org.springframework.stereotype.Repository;import java.util.List;
import java.util.Optional;@Repository
public interface UserRepository extends JpaRepository<User, Long> {Optional<User> findByUserCode(String userCode);List<User> findTop10ByNameContaining(String keyword);
}
上面的Repository虽然没有订制代码,但不妨碍我们用它来演示Repository测试的写法。
以下是对UserRepository进行测试的测试类:
package com.aaa.api.auth.repository;import com.aaa.api.auth.entity.User;
import jakarta.persistence.Query;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.TestPropertySource;import java.util.List;import static org.assertj.core.api.Assertions.assertThat;@DataJpaTest
//@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@TestPropertySource(properties = {//"spring.datasource.driver-class-name=org.h2.Driver",//"spring.datasource.url=jdbc:h2:mem:api_auth_test;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=MySQL;DATABASE_TO_LOWER=TRUE;NON_KEYWORDS=user",//"spring.datasource.username=sa","spring.jpa.hibernate.ddl-auto=create-drop","spring.jpa.properties.hibernate.hbm2ddl.auto=create-drop","spring.jpa.properties.hibernate.globally_quoted_identifiers=true"
})
class UserRepositoryTest {@Autowiredprivate UserRepository repo;@Autowiredprivate TestEntityManager entityManager;private final User u1 = new User();private final User u2 = new User();private final User u3 = new User();@BeforeEachvoid setUp() {u1.setName("张三");u1.setUserCode("zhangsan");u1.setRole(User.ADMIN);u1.setEmail("zhangsan@aaa.net.cn");u1.setMobile("13600001234");u2.setName("李四");u2.setUserCode("lisi");u2.setRole(User.ADMIN);u2.setEmail("lisi@aaa.net.cn");u2.setMobile("13800001234");u3.setName("王五");u3.setUserCode("wangwu");u3.setRole(User.USER);u3.setEmail("wangwu@aaa.net.cn");u3.setMobile("13900001234");}@Testvoid testSave() {Query q = entityManager.getEntityManager().createQuery("from User");repo.save(u1);assertThat(q.getResultList()).hasSize(1);repo.save(u2);assertThat(q.getResultList()).hasSize(2);User u = repo.save(u3);assertThat(q.getResultList()).hasSize(3);u3.setRole(User.ADMIN);repo.save(u3);assertThat(q.getResultList()).hasSize(3);assertThat(entityManager.find(User.class, u.getId()).getRole()).isEqualTo(User.ADMIN);} @Testvoid testFindAll() {entityManager.persist(u1);entityManager.persist(u2);entityManager.persist(u3);List<User> entities = repo.findAll();assertThat(entities).size().isEqualTo(3);assertThat(entities.get(0).getId()).isNotNull();assertThat(entities.get(1).getId()).isNotNull();assertThat(entities.get(2).getId()).isNotNull();}@Testvoid testFindByUserCode() {entityManager.persist(u1);entityManager.persist(u2);entityManager.persist(u3);User u1 = repo.findByUserCode("zhangsan").orElse(null);assertThat(u1).isNotNull();assertThat(u1.getRole()).isEqualTo(User.ADMIN);User u2 = repo.findByUserCode("lisi").orElse(null);assertThat(u2).isNotNull();assertThat(u2.getRole()).isEqualTo(User.ADMIN);User u3 = repo.findByUserCode("wangwu").orElse(null);assertThat(u3).isNotNull();assertThat(u3.getRole()).isEqualTo(User.USER);u3.setRole(User.ADMIN);entityManager.persist(u3);u3 = repo.findByUserCode("wangwu").orElse(null);assertThat(u3).isNotNull();assertThat(u3.getRole()).isEqualTo(User.ADMIN);}@Testvoid testFindTop10ByNameContaining() {entityManager.persist(u2);entityManager.persist(u3);List<User> users = repo.findTop10ByNameContaining("张三");assertThat(users).isEmpty();users = repo.findTop10ByNameContaining("李四");assertThat(users).hasSize(1);users = repo.findTop10ByNameContaining("五");assertThat(users).hasSize(1);}}
测试类说明:
第16行,标注本测试为@DataJpaTest。
18-25行是数据源和JPA属性的订制。
@DataJpaTest、@WebMvcTest以及@SpringBootTest等测试其实是需要Spring Boot的Configuration的;因此我们在测试目录里可能(尤其在多模块工程里)有个专用于测试的Mock Application,注解为@SpringBootApplication(空类,不需要main方法),并且在src/test/resources下还可以有application.properties(或yaml)。而更灵活的方式是使用第18行的@TestPropertySource的properties属性。
@DataJpaTest这个注解会在测试时自动将数据源替换为内存数据库H2的;如果不想要这种替换,或者要订制其url等属性,那么就可以将第17行、第19-21行的注释放开。
第24行的spring.jpa.properties.hibernate.globally_quoted_identifiers=true可以避免数据库保留字与表名/列名冲突。
第29行,我们将待测试类对象作为测试类的一个属性,并使用@Autowired进行注入。
第32行,我们注入了一个TestEntityManager;它作为一个实用工具,帮我们在测试中插数据、查数据,这样就避免了直接使用repo的方法插数据、查数据,因为这些方法本身就是待测目标。
第34-36行提供了三个测试数据,并在setUp()方法中进行初始化/重置。@BeforeEach注解使得setUp()方法在每个测试之前都会执行一遍。
从59行开始,是测试方法。该测试类中,没有任何Mock对象,因此也就不存在given - when - then三段式结构。测试方法都是直接对测试数据进行CRUD操作并检查操作结果,代码都是自解释的。
总结
对于JPA Repository的测试,推荐使用@DataJpaTest注解。
如需订制内存数据库(datasource)的属性,则令@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE),然后在@TestPropertySource.properties里写订制属性。
Repository应设计为单纯的对数据实体的CRUD操作,因此通常不需Mock任何依赖对象。
相关文章:
单元测试实战(三)JPA 的测试
为鼓励单元测试,特分门别类示例各种组件的测试代码并进行解说,供开发人员参考。 本文中的测试均基于JUnit5。 单元测试实战(一)Controller 的测试 单元测试实战(二)Service 的测试 单元测试实战&am…...

初刷leetcode题目(3)——数据结构与算法
😶🌫️😶🌫️😶🌫️😶🌫️Take your time ! 😶🌫️😶🌫️😶🌫️😶🌫️…...

76基于matlab的免疫算法求解配送中心选址问题,根据配送地址确定最佳配送中心地址位置。
基于matlab的免疫算法求解配送中心选址问题,根据配送地址确定最佳配送中心地址位置。数据可更换自己的,程序已调通,可直接运行。 76matlab免疫算法配送中心选址 (xiaohongshu.com)...
C++二分查找算法:找到 Alice 和 Bob 可以相遇的建筑
本文涉及的基础知识点 二分查找算法合集 离线查询 题目 给你一个下标从 0 开始的正整数数组 heights ,其中 heights[i] 表示第 i 栋建筑的高度。 如果一个人在建筑 i ,且存在 i < j 的建筑 j 满足 heights[i] < heights[j] ,那么这个…...
建立跨层全栈的区块链安全保障系统-应用层,系统层,设施层
目录 建立跨层全栈的区块链安全保障系统 应用层 系统层 设施层...

程序员告诉你:人工智能是什么?
随着科技的快速发展,人工智能这个词汇已经逐渐融入了我们的日常生活。然而,对于大多数人来说,人工智能仍然是一个相对模糊的概念。 首先,让我们从人工智能的定义开始。人工智能是一种模拟人类智能的技术,它涵盖了多个领…...

飞书开发学习笔记(七)-添加机器人及发送webhook消息
飞书开发学习笔记(七)-添加机器人及发送webhook消息 一.添加飞书机器人 1.1 添加飞书机器人过程 在群的右上角点击折叠按键…选择 设置 群机器人中选择 添加机器人 选择自定义机器人,通过webhook发送消息 弹出的信息中有webhook地址,选择复制。 安…...

C/C++统计数 2021年12月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析
目录 C/C统计数 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 C/C统计数 2021年12月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 给定一个数的序列S,以及一个区间[L, R], 求序列…...

从一到无穷大 #19 TagTree,倒排索引入手是否是优化时序数据库查询的通用方案?
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。 本作品 (李兆龙 博文, 由 李兆龙 创作),由 李兆龙 确认,转载请注明版权。 文章目录 文章主旨时序数据库查询的一般流程扫描维度聚合时间聚合管控语句 TagTree整体结构索引…...

程序员带你入门人工智能
随着人工智能技术的飞速发展,越来越多的程序员开始关注并学习人工智能。作为程序员,我们可能会对如何开始了解人工智能感到困惑。今天,我将向大家介绍一些如何通过自学了解人工智能的经验和方法,帮助大家更好地入门这个充满挑战和…...
机器学习笔记 - 了解常见开源文本识别数据集以及了解如何创建用于文本识别的合成数据
一、部分开源数据集 以下是一些英文可用的开源文本识别数据集。 ICDAR 数据集:ICDAR 代表国际文档分析和识别会议。该活动每两年举行一次。他们带来了一系列塑造了研究社区的场景文本数据集。例如, ICDAR-2013和ICDAR-2015数据集。 MJSynth 数据集:该合成词数据集由牛津大…...
openssl开发详解
文章目录 一、openssl 开发环境二、openssl随机数生成三、openssl对称加密3.1 SM43.2 AES3.3 DES3.4 3DES 四、openssl非对称加密4.1 SM24.2 RSA4.3 ECC 五、openssl的hash5.1 SM35.2 md55.3 sha256 五、证书5.1 证书格式 六、openssl网络编程七、openssl调试FIDO流程 一、open…...

conda虚拟环境中安装的cuda和服务器上安装的cuda的异同
服务器上已安装Nvidia提供的cuda,nvcc -V时会出现已安装的CUDA版本。如下图所示,服务器上已安装好的cuda版本为10.1。 但是当我们在Anaconda虚拟环境下安装pytorch或者paddlepaddle等深度学习框架的GPU版本时,通常会选择较高版本的cuda&…...

股东入股可用的出资形式主要有哪些
股东入股,可用的出资形式主要包括货币以及实物、知识产权、土地使用权等可以用货币估价并可以依法转让的非货币财产。 第一,货币。设立公司必然需要一定数量的流动资金。以支付创建公司时的开支和启动公司运营。因此,股东可以用货币出资。 第…...
react中设置activeClassName的笔记
React是一种流行的JavaScript库,用于构建动态用户界面。它具有许多有用的组件,其中之一是NavLink组件。NavLink组件用于在React应用程序中创建链接,并且它具有许多有用的属性,例如选中的样式设置。 react-router-dom": “^6…...
JS原型对象prototype
让我简单的为大家介绍一下原型对象prototype吧! 使用原型实现方法共享 1.构造函数通过原型分配的函数是所有对象所 共享的。 2.JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象…...

nodejs+vue实验室上机管理系统的设计与实现-微信小程序-安卓-python-PHP-计算机毕业设计
用户:管理员、教师、学生 基础功能:管理课表、管理机房情况、预约机房预约;权限不同,预约类型不同,教师可选课堂预约和个人;课堂预约。 在实验室上机前,实验室管理员需要对教务处发来的上机课表…...
SpringBoot 注解开发
利用自定义注解,解决问题 例1 自定义注解限制请求 场景:前端发起的频繁的请求,导致服务器压力过大。需要对后端接口进行限流处理,每个接口都需要做限流处理的话就会导致代码冗余,此时就可以利用注解进行解决 非注解形…...

使用持久卷部署 WordPress 和 MySQL
🗓️实验环境 OS名称Microsoft Windows 11 家庭中文版系统类型x64-based PCDocker版本Docker version 24.0.6, build ed223bcminikube版本v1.32.0 🖇️创建 kustomization.yaml 你可以通过 kustomization.yaml 中的生成器创建一个 Secret存储密码或密…...

2024年csdn最新最全的Postman接口测试: postman实现参数化
什么时候会用到参数化 比如:一个模块要用多组不同数据进行测试 验证业务的正确性 Login模块:正确的用户名,密码 成功;错误的用户名,正确的密码 失败 postman实现参数化 在实际的接口测试中,部分参数…...

华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...
pam_env.so模块配置解析
在PAM(Pluggable Authentication Modules)配置中, /etc/pam.d/su 文件相关配置含义如下: 配置解析 auth required pam_env.so1. 字段分解 字段值说明模块类型auth认证类模块,负责验证用户身份&am…...

ElasticSearch搜索引擎之倒排索引及其底层算法
文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...

BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...
【python异步多线程】异步多线程爬虫代码示例
claude生成的python多线程、异步代码示例,模拟20个网页的爬取,每个网页假设要0.5-2秒完成。 代码 Python多线程爬虫教程 核心概念 多线程:允许程序同时执行多个任务,提高IO密集型任务(如网络请求)的效率…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...