测试数据与初始化
在 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
和编程式初始化。
通过合理选择数据初始化策略,可以显著提升测试代码的可读性和可维护性。