From 3a994265d0a366446fde18dabc8c6c344545ee93 Mon Sep 17 00:00:00 2001
From: fbw <1171460872@qq.com>
Date: Mon, 30 Mar 2026 14:59:15 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BE=9D=E8=B5=96=E4=BF=AE=E5=A4=8D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
cc-admin-master/dependencies/pom.xml | 69 +++-------
cc-admin-master/yudao-framework/pom.xml | 1 +
.../yudao-spring-boot-starter-test/pom.xml | 60 +++++++++
.../test/config/RedisTestConfiguration.java | 35 +++++
.../config/SqlInitializationTestConfiguration.java | 52 ++++++++
.../test/core/ut/BaseDbAndRedisUnitTest.java | 55 ++++++++
.../framework/test/core/ut/BaseDbUnitTest.java | 47 +++++++
.../test/core/ut/BaseMockitoUnitTest.java | 13 ++
.../framework/test/core/ut/BaseRedisUnitTest.java | 36 +++++
.../yudao/framework/test/core/ut/package-info.java | 4 +
.../framework/test/core/util/AssertUtils.java | 101 ++++++++++++++
.../framework/test/core/util/RandomUtils.java | 146 +++++++++++++++++++++
.../iocoder/yudao/framework/test/package-info.java | 4 +
...道 Spring Boot 单元测试 Test 入门》.md | 1 +
14 files changed, 576 insertions(+), 48 deletions(-)
create mode 100644 cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/pom.xml
create mode 100644 cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/config/RedisTestConfiguration.java
create mode 100644 cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/config/SqlInitializationTestConfiguration.java
create mode 100644 cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/BaseDbAndRedisUnitTest.java
create mode 100644 cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/BaseDbUnitTest.java
create mode 100644 cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/BaseMockitoUnitTest.java
create mode 100644 cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/BaseRedisUnitTest.java
create mode 100644 cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/package-info.java
create mode 100644 cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/util/AssertUtils.java
create mode 100644 cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/util/RandomUtils.java
create mode 100644 cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/package-info.java
create mode 100644 cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/《芋道 Spring Boot 单元测试 Test 入门》.md
diff --git a/cc-admin-master/dependencies/pom.xml b/cc-admin-master/dependencies/pom.xml
index 252e5ae..36ddde0 100644
--- a/cc-admin-master/dependencies/pom.xml
+++ b/cc-admin-master/dependencies/pom.xml
@@ -5,7 +5,7 @@
4.0.0
cn.iocoder.boot
- dependencies
+ yudao-dependencies
${revision}
pom
@@ -14,7 +14,7 @@
https://github.com/YunaiV/ruoyi-vue-pro
- 2025.08-SNAPSHOT
+ 2.6.1-SNAPSHOT
1.6.0
3.4.5
@@ -24,9 +24,9 @@
1.2.24
3.5.19
- 3.5.12
- 1.5.4
+ 3.5.10.1
4.3.1
+ 1.4.13
3.0.6
3.41.0
8.1.3.140
@@ -54,7 +54,7 @@
1.6.3
5.8.35
6.0.0-M19
- 1.2.0
+ 4.0.3
2.4.1
1.2.83
33.4.8-jre
@@ -69,54 +69,18 @@
0.9.0
4.5.13
+ 2.17.0
+ 1.27.1
2.30.14
1.16.7
1.4.0
2.0.0
1.9.5
4.7.5.B
- 5.2.3
- 2.1.3
- 2.1.3
-
- org.eclipse.angus
- jakarta-mail
- 2.0.5
-
-
- jakarta.mail
- jakarta.mail-api
- ${jakarta.version}
-
-
- org.eclipse.angus
- angus-mail
- ${angus.version}
-
-
-
- com.googlecode.aviator
- aviator
- 5.4.3
-
-
- org.apache.poi
- poi-ooxml
- ${word.version}
-
-
-
-
- org.apache.poi
- poi-scratchpad
- ${word.version}
-
-
-
io.netty
@@ -516,11 +480,20 @@
- cn.idev.excel
- fastexcel
- ${fastexcel.version}
+ com.alibaba
+ easyexcel
+ ${easyexcel.version}
+
+
+ commons-io
+ commons-io
+ ${commons-io.version}
+
+
+ org.apache.commons
+ commons-compress
+ ${commons-compress.version}
-
org.apache.tika
tika-core
@@ -710,4 +683,4 @@
-
+
\ No newline at end of file
diff --git a/cc-admin-master/yudao-framework/pom.xml b/cc-admin-master/yudao-framework/pom.xml
index 20142e0..0e49462 100644
--- a/cc-admin-master/yudao-framework/pom.xml
+++ b/cc-admin-master/yudao-framework/pom.xml
@@ -21,6 +21,7 @@
yudao-spring-boot-starter-protection
yudao-spring-boot-starter-job
yudao-spring-boot-starter-mq
+ yudao-spring-boot-starter-test
yudao-spring-boot-starter-excel
diff --git a/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/pom.xml b/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/pom.xml
new file mode 100644
index 0000000..04576fe
--- /dev/null
+++ b/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/pom.xml
@@ -0,0 +1,60 @@
+
+
+
+ cn.iocoder.boot
+ yudao-framework
+ ${revision}
+
+ 4.0.0
+ yudao-spring-boot-starter-test
+ jar
+
+ ${project.artifactId}
+ 测试组件,用于单元测试、集成测试
+ https://github.com/YunaiV/ruoyi-vue-pro
+
+
+
+ cn.iocoder.boot
+ yudao-common
+
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-mybatis
+
+
+
+ cn.iocoder.boot
+ yudao-spring-boot-starter-redis
+
+
+
+
+ org.mockito
+ mockito-inline
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+
+
+
+ com.h2database
+ h2
+
+
+
+ com.github.fppt
+ jedis-mock
+
+
+
+ uk.co.jemos.podam
+ podam
+
+
+
diff --git a/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/config/RedisTestConfiguration.java b/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/config/RedisTestConfiguration.java
new file mode 100644
index 0000000..4622291
--- /dev/null
+++ b/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/config/RedisTestConfiguration.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.framework.test.config;
+
+import com.github.fppt.jedismock.RedisServer;
+import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
+
+import java.io.IOException;
+
+/**
+ * Redis 测试 Configuration,主要实现内嵌 Redis 的启动
+ *
+ * @author 芋道源码
+ */
+@Configuration(proxyBeanMethods = false)
+@Lazy(false) // 禁止延迟加载
+@EnableConfigurationProperties(RedisProperties.class)
+public class RedisTestConfiguration {
+
+ /**
+ * 创建模拟的 Redis Server 服务器
+ */
+ @Bean
+ public RedisServer redisServer(RedisProperties properties) throws IOException {
+ RedisServer redisServer = new RedisServer(properties.getPort());
+ // 一次执行多个单元测试时,貌似创建多个 spring 容器,导致不进行 stop。这样,就导致端口被占用,无法启动。。。
+ try {
+ redisServer.start();
+ } catch (Exception ignore) {}
+ return redisServer;
+ }
+
+}
diff --git a/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/config/SqlInitializationTestConfiguration.java b/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/config/SqlInitializationTestConfiguration.java
new file mode 100644
index 0000000..abaec9d
--- /dev/null
+++ b/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/config/SqlInitializationTestConfiguration.java
@@ -0,0 +1,52 @@
+package cn.iocoder.yudao.framework.test.config;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
+import org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
+import org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer;
+import org.springframework.boot.sql.init.DatabaseInitializationSettings;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Lazy;
+
+import javax.sql.DataSource;
+
+/**
+ * SQL 初始化的测试 Configuration
+ *
+ * 为什么不使用 org.springframework.boot.autoconfigure.sql.init.DataSourceInitializationConfiguration 呢?
+ * 因为我们在单元测试会使用 spring.main.lazy-initialization 为 true,开启延迟加载。此时,会导致 DataSourceInitializationConfiguration 初始化
+ * 不过呢,当前类的实现代码,基本是复制 DataSourceInitializationConfiguration 的哈!
+ *
+ * @author 芋道源码
+ */
+@Configuration(proxyBeanMethods = false)
+@ConditionalOnMissingBean(AbstractScriptDatabaseInitializer.class)
+@ConditionalOnSingleCandidate(DataSource.class)
+@ConditionalOnClass(name = "org.springframework.jdbc.datasource.init.DatabasePopulator")
+@Lazy(value = false) // 禁止延迟加载
+@EnableConfigurationProperties(SqlInitializationProperties.class)
+public class SqlInitializationTestConfiguration {
+
+ @Bean
+ public DataSourceScriptDatabaseInitializer dataSourceScriptDatabaseInitializer(DataSource dataSource,
+ SqlInitializationProperties initializationProperties) {
+ DatabaseInitializationSettings settings = createFrom(initializationProperties);
+ return new DataSourceScriptDatabaseInitializer(dataSource, settings);
+ }
+
+ static DatabaseInitializationSettings createFrom(SqlInitializationProperties properties) {
+ DatabaseInitializationSettings settings = new DatabaseInitializationSettings();
+ settings.setSchemaLocations(properties.getSchemaLocations());
+ settings.setDataLocations(properties.getDataLocations());
+ settings.setContinueOnError(properties.isContinueOnError());
+ settings.setSeparator(properties.getSeparator());
+ settings.setEncoding(properties.getEncoding());
+ settings.setMode(properties.getMode());
+ return settings;
+ }
+
+}
diff --git a/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/BaseDbAndRedisUnitTest.java b/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/BaseDbAndRedisUnitTest.java
new file mode 100644
index 0000000..46a6927
--- /dev/null
+++ b/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/BaseDbAndRedisUnitTest.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.framework.test.core.ut;
+
+import cn.hutool.extra.spring.SpringUtil;
+import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration;
+import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration;
+import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
+import cn.iocoder.yudao.framework.test.config.RedisTestConfiguration;
+import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration;
+import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure;
+import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
+import org.redisson.spring.starter.RedissonAutoConfiguration;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.jdbc.Sql;
+
+/**
+ * 依赖内存 DB + Redis 的单元测试
+ *
+ * 相比 {@link BaseDbUnitTest} 来说,额外增加了内存 Redis
+ *
+ * @author 芋道源码
+ */
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbAndRedisUnitTest.Application.class)
+@ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件
+@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB
+public class BaseDbAndRedisUnitTest {
+
+ @Import({
+ // DB 配置类
+ YudaoDataSourceAutoConfiguration.class, // 自己的 DB 配置类
+ DataSourceAutoConfiguration.class, // Spring DB 自动配置类
+ DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类
+ DruidDataSourceAutoConfigure.class, // Druid 自动配置类
+ SqlInitializationTestConfiguration.class, // SQL 初始化
+ // MyBatis 配置类
+ YudaoMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类
+ MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类
+
+ // Redis 配置类
+ RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer
+ YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类
+ RedisAutoConfiguration.class, // Spring Redis 自动配置类
+ RedissonAutoConfiguration.class, // Redisson 自动配置类
+
+ // 其它配置类
+ SpringUtil.class
+ })
+ public static class Application {
+ }
+
+}
diff --git a/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/BaseDbUnitTest.java b/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/BaseDbUnitTest.java
new file mode 100644
index 0000000..98b06f9
--- /dev/null
+++ b/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/BaseDbUnitTest.java
@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.framework.test.core.ut;
+
+import cn.hutool.extra.spring.SpringUtil;
+import cn.iocoder.yudao.framework.datasource.config.YudaoDataSourceAutoConfiguration;
+import cn.iocoder.yudao.framework.mybatis.config.YudaoMybatisAutoConfiguration;
+import cn.iocoder.yudao.framework.test.config.SqlInitializationTestConfiguration;
+import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure;
+import com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;
+import com.github.yulichang.autoconfigure.MybatisPlusJoinAutoConfiguration;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.jdbc.Sql;
+
+/**
+ * 依赖内存 DB 的单元测试
+ *
+ * 注意,Service 层同样适用。对于 Service 层的单元测试,我们针对自己模块的 Mapper 走的是 H2 内存数据库,针对别的模块的 Service 走的是 Mock 方法
+ *
+ * @author 芋道源码
+ */
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbUnitTest.Application.class)
+@ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件
+@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后,清理 DB
+public class BaseDbUnitTest {
+
+ @Import({
+ // DB 配置类
+ YudaoDataSourceAutoConfiguration.class, // 自己的 DB 配置类
+ DataSourceAutoConfiguration.class, // Spring DB 自动配置类
+ DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类
+ DruidDataSourceAutoConfigure.class, // Druid 自动配置类
+ SqlInitializationTestConfiguration.class, // SQL 初始化
+ // MyBatis 配置类
+ YudaoMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类
+ MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类
+ MybatisPlusJoinAutoConfiguration.class, // MyBatis 的Join配置类
+
+ // 其它配置类
+ SpringUtil.class
+ })
+ public static class Application {
+ }
+
+}
diff --git a/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/BaseMockitoUnitTest.java b/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/BaseMockitoUnitTest.java
new file mode 100644
index 0000000..2604869
--- /dev/null
+++ b/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/BaseMockitoUnitTest.java
@@ -0,0 +1,13 @@
+package cn.iocoder.yudao.framework.test.core.ut;
+
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+/**
+ * 纯 Mockito 的单元测试
+ *
+ * @author 芋道源码
+ */
+@ExtendWith(MockitoExtension.class)
+public class BaseMockitoUnitTest {
+}
diff --git a/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/BaseRedisUnitTest.java b/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/BaseRedisUnitTest.java
new file mode 100644
index 0000000..ff6315a
--- /dev/null
+++ b/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/BaseRedisUnitTest.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.framework.test.core.ut;
+
+import cn.hutool.extra.spring.SpringUtil;
+import cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration;
+import cn.iocoder.yudao.framework.test.config.RedisTestConfiguration;
+import org.redisson.spring.starter.RedissonAutoConfiguration;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.ActiveProfiles;
+
+/**
+ * 依赖内存 Redis 的单元测试
+ *
+ * 相比 {@link BaseDbUnitTest} 来说,从内存 DB 改成了内存 Redis
+ *
+ * @author 芋道源码
+ */
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseRedisUnitTest.Application.class)
+@ActiveProfiles("unit-test") // 设置使用 application-unit-test 配置文件
+public class BaseRedisUnitTest {
+
+ @Import({
+ // Redis 配置类
+ RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer
+ RedisAutoConfiguration.class, // Spring Redis 自动配置类
+ YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类
+ RedissonAutoConfiguration.class, // Redisson 自动配置类
+
+ // 其它配置类
+ SpringUtil.class
+ })
+ public static class Application {
+ }
+
+}
diff --git a/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/package-info.java b/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/package-info.java
new file mode 100644
index 0000000..bda7aad
--- /dev/null
+++ b/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/ut/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 提供单元测试 Unit Test 的基类
+ */
+package cn.iocoder.yudao.framework.test.core.ut;
diff --git a/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/util/AssertUtils.java b/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/util/AssertUtils.java
new file mode 100644
index 0000000..e98f498
--- /dev/null
+++ b/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/util/AssertUtils.java
@@ -0,0 +1,101 @@
+package cn.iocoder.yudao.framework.test.core.util;
+
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.ReflectUtil;
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
+import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.function.Executable;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Objects;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * 单元测试,assert 断言工具类
+ *
+ * @author 芋道源码
+ */
+public class AssertUtils {
+
+ /**
+ * 比对两个对象的属性是否一致
+ *
+ * 注意,如果 expected 存在的属性,actual 不存在的时候,会进行忽略
+ *
+ * @param expected 期望对象
+ * @param actual 实际对象
+ * @param ignoreFields 忽略的属性数组
+ */
+ public static void assertPojoEquals(Object expected, Object actual, String... ignoreFields) {
+ Field[] expectedFields = ReflectUtil.getFields(expected.getClass());
+ Arrays.stream(expectedFields).forEach(expectedField -> {
+ // 忽略 jacoco 自动生成的 $jacocoData 属性的情况
+ if (expectedField.isSynthetic()) {
+ return;
+ }
+ // 如果是忽略的属性,则不进行比对
+ if (ArrayUtil.contains(ignoreFields, expectedField.getName())) {
+ return;
+ }
+ // 忽略不存在的属性
+ Field actualField = ReflectUtil.getField(actual.getClass(), expectedField.getName());
+ if (actualField == null) {
+ return;
+ }
+ // 比对
+ Assertions.assertEquals(
+ ReflectUtil.getFieldValue(expected, expectedField),
+ ReflectUtil.getFieldValue(actual, actualField),
+ String.format("Field(%s) 不匹配", expectedField.getName())
+ );
+ });
+ }
+
+ /**
+ * 比对两个对象的属性是否一致
+ *
+ * 注意,如果 expected 存在的属性,actual 不存在的时候,会进行忽略
+ *
+ * @param expected 期望对象
+ * @param actual 实际对象
+ * @param ignoreFields 忽略的属性数组
+ * @return 是否一致
+ */
+ public static boolean isPojoEquals(Object expected, Object actual, String... ignoreFields) {
+ Field[] expectedFields = ReflectUtil.getFields(expected.getClass());
+ return Arrays.stream(expectedFields).allMatch(expectedField -> {
+ // 如果是忽略的属性,则不进行比对
+ if (ArrayUtil.contains(ignoreFields, expectedField.getName())) {
+ return true;
+ }
+ // 忽略不存在的属性
+ Field actualField = ReflectUtil.getField(actual.getClass(), expectedField.getName());
+ if (actualField == null) {
+ return true;
+ }
+ return Objects.equals(ReflectUtil.getFieldValue(expected, expectedField),
+ ReflectUtil.getFieldValue(actual, actualField));
+ });
+ }
+
+ /**
+ * 执行方法,校验抛出的 Service 是否符合条件
+ *
+ * @param executable 业务异常
+ * @param errorCode 错误码对象
+ * @param messageParams 消息参数
+ */
+ public static void assertServiceException(Executable executable, ErrorCode errorCode, Object... messageParams) {
+ // 调用方法
+ ServiceException serviceException = assertThrows(ServiceException.class, executable);
+ // 校验错误码
+ Assertions.assertEquals(errorCode.getCode(), serviceException.getCode(), "错误码不匹配");
+ String message = ServiceExceptionUtil.doFormat(errorCode.getCode(), errorCode.getMsg(), messageParams);
+ Assertions.assertEquals(message, serviceException.getMessage(), "错误提示不匹配");
+ }
+
+}
diff --git a/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/util/RandomUtils.java b/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/util/RandomUtils.java
new file mode 100644
index 0000000..1cafbcd
--- /dev/null
+++ b/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/util/RandomUtils.java
@@ -0,0 +1,146 @@
+package cn.iocoder.yudao.framework.test.core.util;
+
+import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import uk.co.jemos.podam.api.PodamFactory;
+import uk.co.jemos.podam.api.PodamFactoryImpl;
+
+import java.lang.reflect.Type;
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 随机工具类
+ *
+ * @author 芋道源码
+ */
+public class RandomUtils {
+
+ private static final int RANDOM_STRING_LENGTH = 10;
+
+ private static final int TINYINT_MAX = 127;
+
+ private static final int RANDOM_DATE_MAX = 30;
+
+ private static final int RANDOM_COLLECTION_LENGTH = 5;
+
+ private static final PodamFactory PODAM_FACTORY = new PodamFactoryImpl();
+
+ static {
+ // 字符串
+ PODAM_FACTORY.getStrategy().addOrReplaceTypeManufacturer(String.class,
+ (dataProviderStrategy, attributeMetadata, map) -> randomString());
+ // Integer
+ PODAM_FACTORY.getStrategy().addOrReplaceTypeManufacturer(Integer.class, (dataProviderStrategy, attributeMetadata, map) -> {
+ // 如果是 status 的字段,返回 0 或 1
+ if ("status".equals(attributeMetadata.getAttributeName())) {
+ return RandomUtil.randomEle(CommonStatusEnum.values()).getStatus();
+ }
+ // 如果是 type、status 结尾的字段,返回 tinyint 范围
+ if (StrUtil.endWithAnyIgnoreCase(attributeMetadata.getAttributeName(),
+ "type", "status", "category", "scope", "result")) {
+ return RandomUtil.randomInt(0, TINYINT_MAX + 1);
+ }
+ return RandomUtil.randomInt();
+ });
+ // LocalDateTime
+ PODAM_FACTORY.getStrategy().addOrReplaceTypeManufacturer(LocalDateTime.class,
+ (dataProviderStrategy, attributeMetadata, map) -> randomLocalDateTime());
+ // Boolean
+ PODAM_FACTORY.getStrategy().addOrReplaceTypeManufacturer(Boolean.class, (dataProviderStrategy, attributeMetadata, map) -> {
+ // 如果是 deleted 的字段,返回非删除
+ if ("deleted".equals(attributeMetadata.getAttributeName())) {
+ return false;
+ }
+ return RandomUtil.randomBoolean();
+ });
+ }
+
+ public static String randomString() {
+ return RandomUtil.randomString(RANDOM_STRING_LENGTH);
+ }
+
+ public static Long randomLongId() {
+ return RandomUtil.randomLong(0, Long.MAX_VALUE);
+ }
+
+ public static Integer randomInteger() {
+ return RandomUtil.randomInt(0, Integer.MAX_VALUE);
+ }
+
+ public static Date randomDate() {
+ return RandomUtil.randomDay(0, RANDOM_DATE_MAX);
+ }
+
+ public static LocalDateTime randomLocalDateTime() {
+ // 设置 Nano 为零的原因,避免 MySQL、H2 存储不到时间戳
+ return LocalDateTimeUtil.of(randomDate()).withNano(0);
+ }
+
+ public static Short randomShort() {
+ return (short) RandomUtil.randomInt(0, Short.MAX_VALUE);
+ }
+
+ public static Set randomSet(Class clazz) {
+ return Stream.iterate(0, i -> i).limit(RandomUtil.randomInt(1, RANDOM_COLLECTION_LENGTH))
+ .map(i -> randomPojo(clazz)).collect(Collectors.toSet());
+ }
+
+ public static Integer randomCommonStatus() {
+ return RandomUtil.randomEle(CommonStatusEnum.values()).getStatus();
+ }
+
+ public static String randomEmail() {
+ return randomString() + "@qq.com";
+ }
+
+ public static String randomMobile() {
+ return "13800138" + RandomUtil.randomNumbers(3);
+ }
+
+ public static String randomURL() {
+ return "https://www.iocoder.cn/" + randomString();
+ }
+
+ @SafeVarargs
+ public static T randomPojo(Class clazz, Consumer... consumers) {
+ T pojo = PODAM_FACTORY.manufacturePojo(clazz);
+ // 非空时,回调逻辑。通过它,可以实现 Pojo 的进一步处理
+ if (ArrayUtil.isNotEmpty(consumers)) {
+ Arrays.stream(consumers).forEach(consumer -> consumer.accept(pojo));
+ }
+ return pojo;
+ }
+
+ @SafeVarargs
+ public static T randomPojo(Class clazz, Type type, Consumer... consumers) {
+ T pojo = PODAM_FACTORY.manufacturePojo(clazz, type);
+ // 非空时,回调逻辑。通过它,可以实现 Pojo 的进一步处理
+ if (ArrayUtil.isNotEmpty(consumers)) {
+ Arrays.stream(consumers).forEach(consumer -> consumer.accept(pojo));
+ }
+ return pojo;
+ }
+
+ @SafeVarargs
+ public static List randomPojoList(Class clazz, Consumer... consumers) {
+ int size = RandomUtil.randomInt(1, RANDOM_COLLECTION_LENGTH);
+ return randomPojoList(clazz, size, consumers);
+ }
+
+ @SafeVarargs
+ public static List randomPojoList(Class clazz, int size, Consumer... consumers) {
+ return Stream.iterate(0, i -> i).limit(size).map(o -> randomPojo(clazz, consumers))
+ .collect(Collectors.toList());
+ }
+
+}
diff --git a/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/package-info.java b/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/package-info.java
new file mode 100644
index 0000000..3a17f51
--- /dev/null
+++ b/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * 测试组件,用于单元测试、集成测试等等
+ */
+package cn.iocoder.yudao.framework.test;
diff --git a/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/《芋道 Spring Boot 单元测试 Test 入门》.md b/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/《芋道 Spring Boot 单元测试 Test 入门》.md
new file mode 100644
index 0000000..c6d0e9a
--- /dev/null
+++ b/cc-admin-master/yudao-framework/yudao-spring-boot-starter-test/《芋道 Spring Boot 单元测试 Test 入门》.md
@@ -0,0 +1 @@
+