2.4 测试数据与初始化

发布于:2025-02-13 ⋅ 阅读:(47) ⋅ 点赞:(0)

测试数据与初始化

在 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 和编程式初始化。

通过合理选择数据初始化策略,可以显著提升测试代码的可读性和可维护性。