事务管理
在 TestContext 框架中,事务由 TransactionalTestExecutionListener 管理,即使您未在测试类上显式声明 @TestExecutionListeners,默认情况下也会配置该监听器。
但是,要启用对事务的支持,您必须在通过 @ContextConfiguration 语义加载的 ApplicationContext 中配置 PlatformTransactionManager Bean(稍后将提供更多详细信息)。
此外,您必须在测试的类或方法级别声明 Infra @Transactional 注解。
测试管理的事务
测试管理的事务是使用 TransactionalTestExecutionListener 声明式管理或使用 TestTransaction(稍后描述)以编程方式管理的事务。
您不应将此类事务与 Infra 管理的事务(由 Infra 在为测试加载的 ApplicationContext 中直接管理的事务)或应用程序管理的事务(在测试调用的应用程序代码中以编程方式管理的事务)混淆。
Infra 管理的和应用程序管理的事务通常参与测试管理的事务。
但是,如果 Infra 管理的或应用程序管理的事务配置了除 REQUIRED 或 SUPPORTS 之外的任何传播类型,则应谨慎(有关详细信息,请参阅关于 事务传播 的讨论)。
|
抢占式超时和测试管理的事务
当结合使用测试框架的任何形式的抢占式超时与 Infra 测试管理的事务时,必须谨慎。 具体来说,Infra 的测试支持在调用当前测试方法 之前 将事务状态绑定到当前线程(通过 可能发生这种情况的情况包括但不限于以下情况。
|
启用和禁用事务
使用 @Transactional 注解测试方法会导致测试在事务中运行,默认情况下,该事务在测试完成后自动回滚。
如果使用 @Transactional 注解测试类,则该类层次结构中的每个测试方法都在事务中运行。
未(在类或方法级别)使用 @Transactional 注解的测试方法不在事务中运行。
请注意,测试生命周期方法不支持 @Transactional——例如,使用 JUnit Jupiter 的 @BeforeAll、@BeforeEach 等注解的方法。
此外,使用 @Transactional 注解但将 propagation 属性设置为 NOT_SUPPORTED 或 NEVER 的测试不在事务中运行。
| 属性 | 支持测试管理的事务 |
|---|---|
|
是 |
|
仅支持 |
|
否 |
|
否 |
|
否 |
|
否:请改用 |
|
否:请改用 |
|
方法级生命周期方法——例如,使用 JUnit Jupiter 的 如果您需要在事务中的套件级或类级生命周期方法中运行代码,您可能希望将相应的 |
请注意,AbstractTransactionalJUnit4InfraContextTests 和 AbstractTransactionalTestNGInfraContextTests 已预先配置为在类级别提供事务支持。
以下示例演示了为基于 Hibernate 的 UserRepository 编写集成测试的常见场景:
-
Java
@JUnitConfig(TestConfig.class)
@Transactional
class HibernateUserRepositoryTests {
@Autowired
HibernateUserRepository repository;
@Autowired
SessionFactory sessionFactory;
JdbcTemplate jdbcTemplate;
@Autowired
void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Test
void createUser() {
// 跟踪测试数据库中的初始状态:
final int count = countRowsInTable("user");
User user = new User(...);
repository.save(user);
// 需要手动刷新以避免测试中的误报
sessionFactory.getCurrentSession().flush();
assertNumUsers(count + 1);
}
private int countRowsInTable(String tableName) {
return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
}
private void assertNumUsers(int expected) {
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
}
}
如 事务回滚和提交行为 中所述,createUser() 方法运行后无需清理数据库,因为对数据库所做的任何更改都会由 TransactionalTestExecutionListener 自动回滚。
事务回滚和提交行为
默认情况下,测试事务将在测试完成后自动回滚;但是,可以通过 @Commit 和 @Rollback 注解以声明方式配置事务提交和回滚行为。
有关更多详细信息,请参阅 注解支持 部分中的相应条目。
编程式事务管理
您可以使用 TestTransaction 中的静态方法以编程方式与测试管理的事务进行交互。
例如,您可以在测试方法、before 方法和 after 方法中使用 TestTransaction 来开始或结束当前测试管理的事务,或者配置当前测试管理的事务以进行回滚或提交。
只要启用 TransactionalTestExecutionListener,就会自动提供对 TestTransaction 的支持。
以下示例演示了 TestTransaction 的一些功能。
有关更多详细信息,请参阅 TestTransaction 的 javadoc。
-
Java
@ContextConfiguration(classes = TestConfig.class)
public class ProgrammaticTransactionManagementTests extends
AbstractTransactionalJUnit4InfraContextTests {
@Test
public void transactionalTest() {
// 断言测试数据库中的初始状态:
assertNumUsers(2);
deleteFromTables("user");
// 对数据库的更改将被提交!
TestTransaction.flagForCommit();
TestTransaction.end();
assertFalse(TestTransaction.isActive());
assertNumUsers(0);
TestTransaction.start();
// 针对数据库执行其他操作,这些操作将在测试完成后自动回滚...
}
protected void assertNumUsers(int expected) {
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
}
}
在事务之外运行代码
有时,您可能需要在事务性测试方法之前或之后但在事务上下文之外运行某些代码——例如,在运行测试之前验证初始数据库状态,或在测试运行之后验证预期的事务提交行为(如果测试配置为提交事务)。
TransactionalTestExecutionListener 支持 @BeforeTransaction 和 @AfterTransaction 注解,正是为了这种场景。
您可以使用这些注解之一注解测试类中的任何 void 方法或测试接口中的任何 void 默认方法,并且 TransactionalTestExecutionListener 确保您的事务前方法或事务后方法在适当的时间运行。
|
一般来说, 但是,从 TODAY Framework 6.1 开始,对于在 JUnit Jupiter 中使用
|
|
任何 before 方法(例如使用 JUnit Jupiter 的 同样,使用 |
配置事务管理器
TransactionalTestExecutionListener 期望在测试的 Infra ApplicationContext 中定义一个 PlatformTransactionManager Bean。
如果在测试的 ApplicationContext 中有多个 PlatformTransactionManager 实例,您可以使用 @Transactional("myTxMgr") 或 @Transactional(transactionManager = "myTxMgr") 声明限定符,或者可以由 @Configuration 类实现 TransactionManagementConfigurer。
有关用于在测试的 ApplicationContext 中查找事务管理器的算法的详细信息,请参阅 TestContextTransactionUtils.retrieveTransactionManager() 的 javadoc。
所有事务相关注解的演示
以下基于 JUnit Jupiter 的示例展示了一个虚构的集成测试场景,突出显示了所有与事务相关的注解。
该示例并非旨在演示最佳实践,而是为了演示如何使用这些注解。
有关更多信息和配置示例,请参阅 注解支持 部分。
@Sql 的事务管理 包含一个额外的示例,该示例使用 @Sql 进行具有默认事务回滚语义的声明性 SQL 脚本执行。
以下示例显示了相关的注解:
-
Java
@JUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {
@BeforeTransaction
void verifyInitialDatabaseState() {
// 在启动事务之前验证初始状态的逻辑
}
@BeforeEach
void setUpTestDataWithinTransaction() {
// 在事务中设置测试数据
}
@Test
// 覆盖类级 @Commit 设置
@Rollback
void modifyDatabaseWithinTransaction() {
// 使用测试数据并修改数据库状态的逻辑
}
@AfterEach
void tearDownWithinTransaction() {
// 在事务中运行 "拆解" 逻辑
}
@AfterTransaction
void verifyFinalDatabaseState() {
// 在事务回滚后验证最终状态的逻辑
}
}
|
测试 ORM 代码时避免误报
当您测试操作 Hibernate 会话或 JPA 持久性上下文状态的应用程序代码时,请务必在运行该代码的测试方法中刷新底层工作单元。 未能刷新底层工作单元可能会产生误报:您的测试通过了,但相同的代码在实时生产环境中抛出异常。 请注意,这适用于任何维护内存中工作单元的 ORM 框架。 在以下基于 Hibernate 的示例测试用例中,一个方法演示了误报,另一个方法正确公开了刷新会话的结果:
以下示例显示了 JPA 的匹配方法:
|
|
测试 ORM 实体生命周期回调
类似于关于在测试 ORM 代码时避免 误报 的说明,如果您的应用程序使用了实体生命周期回调(也称为实体监听器),请务必在运行该代码的测试方法中刷新底层工作单元。 未能 刷新 (flush) 或 清除 (clear) 底层工作单元可能会导致某些生命周期回调未被调用。 例如,当使用 JPA 时,除非在保存或更新实体后调用 以下示例显示了如何刷新
请参阅 TODAY Framework 测试套件中的 JpaEntityListenerTests, 了解使用所有 JPA 生命周期回调的工作示例。 |