每次执行@Test方法前都执行一次DB初始化(SpringBoot Test + JUnit5环境)

引言

在执行单元测试时,可以使用诸如H2内存数据库替代线上的Mysql数据库等,如此在执行单元测试时就能尽可能模拟真实环境的SQL执行,同时也无需依赖线上数据库,增加了测试用例执行环境的可移植性。而使用H2数据库时,通常会在执行单元测试前先初始化数据库,即执行SQL脚本来对H2内存数据库进行初始化。
例如可通过如下配置指定H2的初始化脚本:

注: 如下配置中的
spring.sql.init.schema-locations
spring.sql.init.data-locations
即对应数据库schema(table定义)、data(数据插入)的导入脚本。

spring:# Sql初始化配置sql:init:# 导入h2 table定义schema-locations: classpath:h2/demo-schema.sql# 导入h2 数据定义data-locations: classpath:h2/demo-data.sql# 数据库配置datasource:type: com.zaxxer.hikari.HikariDataSource# ============================================================# ============= 使用H2内存数据库 ================================# ============================================================driver-class-name: org.h2.Driver# 使用h2内存数据(以mysql兼容模式运行)url: jdbc:h2:mem:rbac;MODE=MySQL;DATABASE_TO_LOWER=TRUEusername: rootpassword: 123456

相关SQL脚本示例如下:

-- h2/demo-schema.sql定义
-- 注意create table前都先执行drop table脚本,如此便可保证Sql脚本可以被覆盖执行
DROP TABLE IF EXISTS category;
CREATE TABLE category (id bigint(20) NOT NULL COMMENT '主键ID',parent_category_id bigint(20) DEFAULT NULL COMMENT '上级分类ID',category_name varchar(64) NOT NULL COMMENT '分类名称',category_desc varchar(512) DEFAULT NULL COMMENT '分类描述',create_time datetime NOT NULL COMMENT '创建时间',update_time datetime DEFAULT NULL COMMENT '修改时间'
) ENGINE = InnoDB;-- ------------------------------------------------------------------------
-- h2/demo-data.sql定义
-- 注意先执行schema定义sql,再执行data定义sql,多个sql脚本需考虑好先后执行顺序
INSERT INTO category
(id,   parent_category_id, category_name, category_desc,      create_time,                update_time) VALUES
( 1,   null,               '分类1',       '查询测试专用数据',    '2023-01-01T15:58:04.105',   '2023-01-07T15:58:04.105'),
( 11,   1,                 '分类1-1',     '分类1-1描述',        '2023-01-02T15:58:04.105',   '2023-01-07T15:58:04.105');

但问题是如上SQL初始化配置会在Spring环境启动时仅执行一次,后续的单元测试都是作用在这同一套脚本的基础之上,不同的单元测试都对这套基础数据进行修改之后,可能会导致数据混乱,进而导致单元测试执行失败。

比较理想的状态就是在每个单元测试方法执行之前,都执行一遍数据库初始化,如此便能保证每个单元测试方法执行前的数据库数据都是固定的,不受其他单元测试的影响,如此在编写单元测试时都以统一的基础数据为基准,无需考虑前后单元测试间的数据依赖,减轻了单元测试方法的开发难度。

接下来本文主要介绍两种在每次单元测试方法执行前都会通过SQL脚本对数据库进行始化的方式。

方式1: @Sql

可在单元测试类 或者 单元测试方法上使用@Sql注解,

import org.springframework.test.context.jdbc.Sql;@Sql(//SQL初始化脚本(会按照数组声明顺序依次执行Sql脚本)scripts = {"classpath:h2/schema.sql","classpath:h2/data.sql"},//SQL初始化执行阶段(BEFORE_TEST_METHOD: 测试方法执行前, AFTER_TEST_METHOD: 测试方法执行后)executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD
)
@SpringBootTest
public class MyBizTest {//具体方法上也可使用@Sql注解,此处@Sql定义会覆盖类上的@Sql//可通过在测试方法 或 测试类上 使用@SqlMergeMode(MergeMode.OVERRIDE|MERGE)来设置方法的@Sql是覆盖、还是合并类上的@Sql@Testvoid testMyBiz() {//......}
}

如果定义了基础测试类,也可将@Sql直接定义在基础测试类上:

import org.springframework.test.context.jdbc.Sql;@Sql(//SQL初始化脚本(会按照数组声明顺序依次执行Sql脚本)scripts = {"classpath:rbac-h2/schema.sql","classpath:rbac-h2/data.sql"},//SQL初始化执行阶段(BEFORE_TEST_METHOD: 方法执行前, AFTER_TEST_METHOD: 方法执行后)executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD
)
@SpringBootTest
public class BaseTest {//......
}---public class MyBizTest extends BaseTest {@Testvoid testMyBiz() {//......}
}

对@Sql的支持是由org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener提供的,并且它是默认启用的。
在这里插入图片描述
关于@Sql、@SqlConfig、@SqlMergeMode、@SqlGroup的更多使用可参见:
https://docs.spring.io/spring-framework/docs/5.3.29/reference/html/testing.html#testcontext-executing-sql-declaratively

方式2:ResourceDatabasePopulator

除了通过注解的方式,也可以通过编程的方式执行数据库初始脚本。Spring提供了如下执行SQL脚本的工具类:

  • org.springframework.jdbc.datasource.init.ScriptUtils
  • org.springframework.jdbc.datasource.init.ResourceDatabasePopulator
  • org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests
  • org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests

ScriptUtils提供了一组用于处理SQL脚本的静态实用程序方法,主要用于框架内部使用。然而,如果您需要完全控制SQL脚本的解析和运行方式,ScriptUtils可能比稍后介绍的其他一些替代方案更适合您的需求。更多细节请参考ScriptUtils中各个方法的javadoc。

ResourceDatabasePopulator提供了一个基于对象的API,通过使用在外部资源中定义的SQL脚本以编程方式填充、初始化或清理数据库。ResourceDatabasePopulator提供了配置在解析和运行脚本时使用的字符编码、语句分隔符、注释分隔符和错误处理标志的选项。每个配置选项都有一个合理的默认值。有关默认值的详细信息,请参阅javadoc。要运行在ResourceDatabasePopulator中配置的脚本,您可以调用populate(Connection)方法对java.sql.Connection运行populator,或者调用execute(DataSource)方法对javax.sql.DataSource运行populator。

ResourceDatabasePopulator在内部委托ScriptUtils解析和运行SQL脚本。类似地,AbstractTransactionalJUnit4SpringContextTests和AbstractTransactionalTestNGSpringContextTests中的executeSqlScript(…)方法在内部使用ResourceDatabasePopulator来运行SQL脚本。

如下使用ResourceDatabasePopulator ,结合@BeforeEach在每个@Test方法执行前对数据库进行初始化:

@SpringBootTest
public class MyBizTest {//注入数据源@Resourceprivate DataSource dataSource;//@BeforeEach即对应没个@Test方法执行前,//亦可通过@BeforeAll指定在每个测试类执行前执行(需在测试类上标注@TestInstance(TestInstance.Lifecycle.PER_CLASS))@BeforeEach public void beforeEachTestMethod() {//声明ResourceDatabasePopulator ResourceDatabasePopulator populator = new ResourceDatabasePopulator();//加载SQL脚本,此处直接使用ClassPathResource, 所以具体path无需以classpath:开头//会按照数组声明顺序依次执行Sql脚本ClassPathResource[] scriptResources = Stream.of("h2/schema.sql","h2/data.sql").map(ClassPathResource::new).toArray(ClassPathResource[]::new);//添加SQL脚本populator.addScripts(scriptResources);//使用数据源执行SQL脚本populator.execute(this.dataSource);}@Testvoid testMyBiz() {//......}    
}

如果定义了基础测试类,也可将@BeforeEach逻辑直接定义在基础测试类上:

@SpringBootTest
public class BaseTest {//注入数据源@Resourceprivate DataSource dataSource;    //@BeforeEach即对应没个@Test方法执行前,//亦可通过@BeforeAll指定在每个测试类执行前执行(需在测试类上标注@TestInstance(TestInstance.Lifecycle.PER_CLASS))@BeforeEach public void beforeEachTestMethod() {//声明ResourceDatabasePopulator ResourceDatabasePopulator populator = new ResourceDatabasePopulator();//加载SQL脚本,此处直接使用ClassPathResource, 所以具体path无需以classpath:开头//会按照数组声明顺序依次执行Sql脚本ClassPathResource[] scriptResources = Stream.of("h2/schema.sql","h2/data.sql").map(ClassPathResource::new).toArray(ClassPathResource[]::new);//添加SQL脚本populator.addScripts(scriptResources);//使用数据源执行SQL脚本populator.execute(this.dataSource);}
}---public class MyBizTest extends BaseTest {@Testvoid testMyBiz() {//......}
}

参考:
https://docs.spring.io/spring-framework/docs/5.3.29/reference/html/testing.html#testcontext-executing-sql

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

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

相关文章

C#程序的启动显示方案(无窗口进程发送消息) - 开源研究系列文章

今天继续研究C#的WinForm的实例显示效果。 我们上次介绍了Winform窗体的唯一实例运行代码(见博文:基于C#的应用程序单例唯一运行的完美解决方案 - 开源研究系列文章 )。这就有一个问题,程序已经打开了,这时候再次运行该应用程序,…

16 Springboot——登录功能实现

16.1 修改index.html中表单跳转的地址 将action的地址改为user/login&#xff0c;意思是点击提交按钮后&#xff0c;就会跳转到user/login地址&#xff0c;然后只要用Controller类的RequsetMapping去接这个地址就行了。 <body class"text-center"><form cl…

Spring 是如何解决循环依赖问题的?

项目场景&#xff1a; 提示&#xff1a;这里简述项目相关背景&#xff1a; 例如&#xff1a;项目场景&#xff1a;示例:通过蓝牙芯片(HC-05)与手机 APP 通信&#xff0c;每隔 5s 传输一批传感器数据(不是很大) 问题描述 我们都知道&#xff0c;如果在代码中&#xff0c;将两个…

记录一次因为代码混淆导致的安卓app崩溃的事件

最近公司布置了一个新任务&#xff0c;给一个旧的安卓app增加一个新功能。 功能是替换加密算法&#xff0c;新的算法库由第三法提供&#xff0c;通过jni调用底层C库。 按照项目需求&#xff0c;修改了代码&#xff0c;调试测试阶段也都运行正常。结果发布的时候&#xff0c;测…

视频网站如何选择国外服务器?

​ 视频网站如何选择国外服务器? 地理位置&#xff1a;选择靠近目标用户群体的国外服务器位置是至关重要的。若用户主要集中在中国以外的地区&#xff0c;因您应选择位于用户所在地附近的服务商&#xff0c;以确保视频的传输速度。 带宽和速度&#xff1a;选择带宽足够且方便升…

【论文阅读】对抗溯源图主机入侵检测系统的模仿攻击(NDSS-2023)

作者&#xff1a;伊利诺伊大学芝加哥分校-Akul Goyal、Gang Wang、Adam Bates&#xff1b;维克森林大学-Xueyuan Han、 引用&#xff1a;Goyal A, Han X, Wang G, et al. Sometimes, You Aren’t What You Do: Mimicry Attacks against Provenance Graph Host Intrusion Detect…

基于kubeadm部署K8S集群

目录 基于kubeadm部署K8S集群 一、环境准备 1、主机初始化配置 2、配置主机名并绑定hosts&#xff0c;不同主机名称不同 3、主机配置初始化 二、部署docker环境 1、三台主机上分别部署 Docker 环境 2、镜像加速器&#xff08;所有主机配置&#xff09; 三、部署kubern…

Maven基础总结

前言 Maven 是一个项目管理工具&#xff0c;可以对 Java 项目进行构建、依赖管理。 基本要求掌握 配置Maven环境直接查。 得会在IDEA创建Maven的java项目吧、会创建Maven的web项目吧、会创建多模块项目吧。 得会配置插件pligin、依赖dependency吧 一、Maven四大特性 1、…

【springboot项目】在idea中启动报错合集

一、IDEA中报错 “Error running ‘Application‘: Command line is too long.“ 的解决办法 报错详情&#xff1a; Error running Application: Command line is too long.Shorten command line for Application or also for Spring Boot default configuration.报错原因&am…

【香瓜说职场】建立公司(2017.07.25)

自从17年4月份开始辞职创业&#xff0c;已经3个多月了。跟大家分享一下创业经历。 一、我的合伙人 我的合伙人是我的客户。我给他上过蓝牙教学课、帮他做了个蓝牙自拍器&#xff08;叫orbit 360&#xff0c;已在售&#xff0c;百度能搜到&#xff09;&#xff0c;历经一年多。双…

objectMapper.getTypeFactory().constructParametricType 方法的作用和使用

在使用 Jackson 库进行 JSON 数据的序列化和反序列化时&#xff0c;经常会使用到 ObjectMapper 类。其中&#xff0c;objectMapper.getTypeFactory().constructParametricType 方法用于构造泛型类型。 具体作用和使用如下&#xff1a; 作用&#xff1a; 构造泛型类型&#x…

吐血整理,Python接口自动化测试-接口关联依赖处理(详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 场景说明 在面试…