事務管理
在 TestContext 框架中,事務由 TransactionalTestExecutionListener 管理,即使您沒有在測試類上顯式宣告 @TestExecutionListeners,它也預設配置。但是,要啟用事務支援,您必須在透過 @ContextConfiguration 語義載入的 ApplicationContext 中配置一個 PlatformTransactionManager bean(稍後將提供更多詳細資訊)。此外,您必須在測試的類或方法級別宣告 Spring 的 @Transactional 註解。
測試管理的事務
測試管理的事務是透過使用 TransactionalTestExecutionListener 宣告性地管理或透過使用 TestTransaction 程式設計性地管理(稍後描述)的事務。您不應將此類事務與 Spring 管理的事務(在為測試載入的 ApplicationContext 中直接由 Spring 管理的事務)或應用程式管理的事務(在由測試呼叫的應用程式程式碼中程式設計性管理的事務)混淆。Spring 管理和應用程式管理的事務通常參與測試管理的事務。但是,如果 Spring 管理或應用程式管理的事務配置了 REQUIRED 或 SUPPORTS 之外的任何傳播型別,則應謹慎使用(有關詳細資訊,請參閱關於事務傳播的討論)。
|
搶佔式超時和測試管理的事務
將測試框架的任何形式的搶佔式超時與 Spring 的測試管理的事務結合使用時必須謹慎。 具體來說,Spring 的測試支援在呼叫當前測試方法之前將事務狀態繫結到當前執行緒(透過 可能發生這種情況的情況包括但不限於以下內容。
|
啟用和停用事務
用 @Transactional 註解測試方法會導致測試在事務中執行,該事務預設在測試完成後自動回滾。如果測試類用 @Transactional 註解,則該類層次結構中的每個測試方法都在事務中執行。未用 @Transactional 註解(在類或方法級別)的測試方法不在事務中執行。請注意,@Transactional 不支援在測試生命週期方法上使用,例如,用 JUnit Jupiter 的 @BeforeAll、@BeforeEach 等註解的方法。此外,用 @Transactional 註解但將 propagation 屬性設定為 NOT_SUPPORTED 或 NEVER 的測試不在事務中執行。
| 屬性 | 支援測試管理的事務 |
|---|---|
|
是 |
|
僅支援 |
|
no |
|
no |
|
no |
|
否:請改用 |
|
否:請改用 |
|
方法級別的生命週期方法(例如,用 JUnit Jupiter 的 如果您需要在套件級別或類級別的生命週期方法中執行程式碼,您可能希望將相應的 |
請注意,AbstractTransactionalJUnit4SpringContextTests 和 AbstractTransactionalTestNGSpringContextTests 已預配置為類級別的事務支援。
以下示例演示了為基於 Hibernate 的 UserRepository 編寫整合測試的常見場景
-
Java
-
Kotlin
@SpringJUnitConfig(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() {
// track initial state in test database:
final int count = countRowsInTable("user");
User user = new User(...);
repository.save(user);
// Manual flush is required to avoid false positive in test
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"));
}
}
@SpringJUnitConfig(TestConfig::class)
@Transactional
class HibernateUserRepositoryTests {
@Autowired
lateinit var repository: HibernateUserRepository
@Autowired
lateinit var sessionFactory: SessionFactory
lateinit var jdbcTemplate: JdbcTemplate
@Autowired
fun setDataSource(dataSource: DataSource) {
this.jdbcTemplate = JdbcTemplate(dataSource)
}
@Test
fun createUser() {
// track initial state in test database:
val count = countRowsInTable("user")
val user = User()
repository.save(user)
// Manual flush is required to avoid false positive in test
sessionFactory.getCurrentSession().flush()
assertNumUsers(count + 1)
}
private fun countRowsInTable(tableName: String): Int {
return JdbcTestUtils.countRowsInTable(jdbcTemplate, tableName)
}
private fun assertNumUsers(expected: Int) {
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
-
Kotlin
@ContextConfiguration(classes = TestConfig.class)
public class ProgrammaticTransactionManagementTests extends
AbstractTransactionalJUnit4SpringContextTests {
@Test
public void transactionalTest() {
// assert initial state in test database:
assertNumUsers(2);
deleteFromTables("user");
// changes to the database will be committed!
TestTransaction.flagForCommit();
TestTransaction.end();
assertFalse(TestTransaction.isActive());
assertNumUsers(0);
TestTransaction.start();
// perform other actions against the database that will
// be automatically rolled back after the test completes...
}
protected void assertNumUsers(int expected) {
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
}
}
@ContextConfiguration(classes = [TestConfig::class])
class ProgrammaticTransactionManagementTests : AbstractTransactionalJUnit4SpringContextTests() {
@Test
fun transactionalTest() {
// assert initial state in test database:
assertNumUsers(2)
deleteFromTables("user")
// changes to the database will be committed!
TestTransaction.flagForCommit()
TestTransaction.end()
assertFalse(TestTransaction.isActive())
assertNumUsers(0)
TestTransaction.start()
// perform other actions against the database that will
// be automatically rolled back after the test completes...
}
protected fun assertNumUsers(expected: Int) {
assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"))
}
}
在事務之外執行程式碼
有時,您可能需要在事務性測試方法之前或之後執行某些程式碼,但要在事務性上下文之外執行——例如,在執行測試之前驗證初始資料庫狀態,或在測試執行後驗證預期的事務提交行為(如果測試配置為提交事務)。TransactionalTestExecutionListener 支援 @BeforeTransaction 和 @AfterTransaction 註解,正是為了應對這種情況。您可以用其中一個註解標記測試類中的任何 void 方法或測試介面中的任何 void 預設方法,並且 TransactionalTestExecutionListener 確保您的事務前方法或事務後方法在適當的時間執行。
|
一般來說, 但是,對於使用 JUnit Jupiter 的
|
|
任何 before 方法(例如用 JUnit Jupiter 的 同樣,用 |
配置事務管理器
TransactionalTestExecutionListener 期望在測試的 Spring ApplicationContext 中定義一個 PlatformTransactionManager bean。如果測試的 ApplicationContext 中有多個 PlatformTransactionManager 例項,您可以使用 @Transactional("myTxMgr") 或 @Transactional(transactionManager = "myTxMgr") 宣告一個限定符,或者 TransactionManagementConfigurer 可以由 @Configuration 類實現。有關在測試的 ApplicationContext 中查詢事務管理器所用演算法的詳細資訊,請參閱TestContextTransactionUtils.retrieveTransactionManager() 的 javadoc。
所有事務相關注解的演示
以下基於 JUnit Jupiter 的示例顯示了一個虛構的整合測試場景,其中突出顯示了所有事務相關注解。該示例無意演示最佳實踐,而是演示如何使用這些註解。有關更多資訊和配置示例,請參閱註解支援部分。@Sql 的事務管理包含一個附加示例,該示例使用 @Sql 進行宣告性 SQL 指令碼執行,並具有預設事務回滾語義。以下示例顯示了相關注解
-
Java
-
Kotlin
@SpringJUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {
@BeforeTransaction
void verifyInitialDatabaseState() {
// logic to verify the initial state before a transaction is started
}
@BeforeEach
void setUpTestDataWithinTransaction() {
// set up test data within the transaction
}
@Test
// overrides the class-level @Commit setting
@Rollback
void modifyDatabaseWithinTransaction() {
// logic which uses the test data and modifies database state
}
@AfterEach
void tearDownWithinTransaction() {
// run "tear down" logic within the transaction
}
@AfterTransaction
void verifyFinalDatabaseState() {
// logic to verify the final state after transaction has rolled back
}
}
@SpringJUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {
@BeforeTransaction
fun verifyInitialDatabaseState() {
// logic to verify the initial state before a transaction is started
}
@BeforeEach
fun setUpTestDataWithinTransaction() {
// set up test data within the transaction
}
@Test
// overrides the class-level @Commit setting
@Rollback
fun modifyDatabaseWithinTransaction() {
// logic which uses the test data and modifies database state
}
@AfterEach
fun tearDownWithinTransaction() {
// run "tear down" logic within the transaction
}
@AfterTransaction
fun verifyFinalDatabaseState() {
// logic to verify the final state after transaction has rolled back
}
}
|
測試 ORM 程式碼時避免假陽性
當您測試操作 Hibernate 會話或 JPA 持久化上下文狀態的應用程式程式碼時,請確保在執行該程式碼的測試方法中重新整理底層工作單元。未能重新整理底層工作單元可能會導致假陽性:您的測試透過,但相同的程式碼在實際生產環境中會丟擲異常。請注意,這適用於任何維護記憶體中工作單元的 ORM 框架。在以下基於 Hibernate 的示例測試用例中,一個方法演示了假陽性,另一個方法正確地暴露了重新整理會話的結果
以下示例顯示了 JPA 的匹配方法
|
|
測試 ORM 實體生命週期回撥
與關於避免測試 ORM 程式碼時出現假陽性的注意事項類似,如果您的應用程式使用實體生命週期回撥(也稱為實體監聽器),請確保在執行該程式碼的測試方法中重新整理底層工作單元。未能重新整理或清除底層工作單元可能導致某些生命週期回撥未被呼叫。 例如,在使用 JPA 時,除非在實體儲存或更新後呼叫 以下示例演示瞭如何重新整理
有關使用所有 JPA 生命週期回撥的實際示例,請參閱 Spring 框架測試套件中的JpaEntityListenerTests。 |