测试数据与初始化
在 Spring Test 中,合理管理测试数据的初始化和清理是保证测试可靠性的关键。本章将介绍多种数据准备方式,涵盖 SQL 脚本执行、编程式初始化 和 动态数据生成,并提供最佳实践示例。
1. 使用 @Sql
执行 SQL 脚本
作用
- 在测试方法前后执行 SQL 脚本:快速初始化或清理数据库。
- 支持事务内/外执行(默认在事务内)。
核心属性
属性 | 作用 |
---|---|
scripts |
指定 SQL 脚本路径 |
executionPhase |
执行阶段(BEFORE_TEST_METHOD / AFTER_TEST_METHOD) |
config |
自定义脚本配置(如注释分隔符) |
示例代码
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = DataSourceConfig.class)
@Transactional
public class SqlAnnotationTest {
@Autowired
private JdbcTemplate jdbcTemplate;
// 测试前执行初始化脚本
@Test
@Sql(scripts = "classpath:init-data.sql")
void testWithInitialData() {
Integer count = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM users", Integer.class);
assertEquals(2, count); // init-data.sql 插入了 2 条记录
}
// 测试后执行清理脚本
@Test
@Sql(
scripts = "classpath:cleanup-data.sql",
executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD
)
void testWithCleanup() {
jdbcTemplate.update("INSERT INTO users (name) VALUES ('Alice')");
}
}
init-data.sql 内容:
INSERT INTO users (id, name) VALUES (1, 'TestUser1');
INSERT INTO users (id, name) VALUES (2, 'TestUser2');
cleanup-data.sql 内容:
DELETE FROM users WHERE name = 'Alice';
2. 编程式数据初始化
使用 JdbcTemplate
动态操作数据
适用于需要灵活生成数据的场景(如随机值、循环插入)。
示例代码
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = DataSourceConfig.class)
@Transactional
public class ProgrammaticDataTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@BeforeEach
void setupData() {
// 在每个测试前插入基础数据
jdbcTemplate.update("INSERT INTO users (name) VALUES ('BaseUser')");
}
@Test
void testDynamicData() {
// 动态插入测试数据
jdbcTemplate.update("INSERT INTO users (name) VALUES (?)", "DynamicUser");
Integer count = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM users", Integer.class);
assertEquals(2, count); // BaseUser + DynamicUser
}
@AfterEach
void cleanupData() {
// 清理数据(通常由事务回滚处理,此处仅为示例)
jdbcTemplate.update("DELETE FROM users WHERE name IN ('BaseUser', 'DynamicUser')");
}
}
3. 参数化测试与动态数据
结合 JUnit 5 的 @ParameterizedTest
实现数据驱动测试。
示例代码
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = DataSourceConfig.class)
@Transactional
public class ParameterizedDataTest {
@Autowired
private UserRepository userRepository;
// 定义参数化数据源
@ParameterizedTest
@ValueSource(strings = {"Alice", "Bob", "Charlie"})
void testCreateUsers(String name) {
User user = new User(name);
userRepository.save(user);
assertNotNull(user.getId());
}
// 使用 CSV 数据源
@ParameterizedTest
@CsvSource({"1, Alice", "2, Bob"})
void testFindUser(Long id, String expectedName) {
User user = userRepository.findById(id).orElseThrow();
assertEquals(expectedName, user.getName());
}
}
4. 事务回滚自动清理数据
利用 @Transactional
的默认回滚机制,无需手动清理。
示例场景
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = DataSourceConfig.class)
@Transactional // 类级别启用事务
public class TransactionalCleanupTest {
@Autowired
private UserService userService;
@Test
void testUserCreation() {
userService.createUser("Alice");
// 事务回滚后,Alice 不会持久化到数据库
}
@Test
@Commit
void testPersistentUser() {
userService.createUser("Bob");
// 提交事务,Bob 会持久化到数据库
}
}
5. 自定义数据初始化工具
场景需求
在多测试类中复用数据初始化逻辑,减少重复代码。
实现步骤
步骤 1:创建数据工具类
public class TestDataUtils {
public static void insertUser(JdbcTemplate jdbcTemplate, String name) {
jdbcTemplate.update("INSERT INTO users (name) VALUES (?)", name);
}
public static void clearUsers(JdbcTemplate jdbcTemplate) {
jdbcTemplate.update("DELETE FROM users");
}
}
步骤 2:在测试类中使用工具类
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = DataSourceConfig.class)
@Transactional
public class DataUtilTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@BeforeEach
void setup() {
TestDataUtils.insertUser(jdbcTemplate, "ToolUser");
}
@Test
void testDataUtil() {
Integer count = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM users", Integer.class);
assertEquals(1, count);
}
}
6. 最佳实践总结
场景 | 推荐方式 | 优点 |
---|---|---|
固定数据初始化 | @Sql 注解加载 SQL 脚本 |
维护简单,数据与代码分离 |
动态数据生成 | 编程式操作(JdbcTemplate ) |
灵活性高,支持复杂逻辑 |
多测试数据复用 | 自定义工具类 | 减少代码冗余,提升可维护性 |
参数化测试 | JUnit 5 @ParameterizedTest |
数据驱动,覆盖多种边界条件 |
注意事项:
- 避免在测试中依赖外部数据库的持久化数据。
- 始终优先使用事务回滚确保测试隔离性。
- 对于复杂数据场景,可结合
@Sql
和编程式初始化。
通过合理选择数据初始化策略,可以显著提升测试代码的可读性和可维护性。