交易管理
在 TestContext framework 中,交易由 TransactionalTestExecutionListener
管理,即使您未在測試類別上明確宣告 @TestExecutionListeners
,預設也會組態此監聽器。但是,若要啟用交易支援,您必須在透過 @ContextConfiguration
語意載入的 ApplicationContext
中組態 PlatformTransactionManager
bean (稍後提供更多詳細資訊)。此外,您必須在測試的類別或方法層級宣告 Spring 的 @Transactional
註解。
測試管理交易
測試管理交易是使用 TransactionalTestExecutionListener
以宣告方式管理,或使用 TestTransaction
(稍後說明) 以程式設計方式管理的交易。您不應將此類交易與 Spring 管理交易 (由 Spring 直接在為測試載入的 ApplicationContext
中管理的交易) 或應用程式管理交易 (在測試叫用的應用程式碼中以程式設計方式管理的交易) 混淆。Spring 管理和應用程式管理交易通常會參與測試管理交易。但是,如果 Spring 管理或應用程式管理交易組態為任何傳播類型而非 REQUIRED
或 SUPPORTS
,則應謹慎使用 (請參閱 交易傳播 的討論以取得詳細資訊)。
先佔逾時和測試管理交易
將測試框架的任何形式的先佔逾時與 Spring 的測試管理交易結合使用時,必須格外小心。 具體而言,Spring 的測試支援在叫用目前測試方法之前,將交易狀態繫結至目前執行緒 (透過 可能發生這種情況的情況包括但不限於下列各項。
|
啟用和停用交易
使用 @Transactional
註解測試方法會導致測試在交易內執行,預設情況下,測試完成後會自動回滾。如果測試類別使用 @Transactional
註解,則該類別階層中的每個測試方法都會在交易內執行。未在類別或方法層級使用 @Transactional
註解的測試方法不會在交易內執行。請注意,測試生命週期方法 (例如,使用 JUnit Jupiter 的 @BeforeAll
、@BeforeEach
等註解的方法) 不支援 @Transactional
。此外,使用 @Transactional
註解但將 propagation
屬性設定為 NOT_SUPPORTED
或 NEVER
的測試不會在交易內執行。
屬性 | 測試管理交易支援 |
---|---|
|
是 |
|
僅支援 |
|
否 |
|
否 |
|
否 |
|
否:請改用 |
|
否:請改用 |
方法層級生命週期方法 (例如,使用 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
會確保您的 before-transaction 方法或 after-transaction 方法在適當的時間執行。
一般而言, 但是,從 Spring Framework 6.1 開始,對於使用
|
任何 before 方法 (例如使用 JUnit Jupiter 的 同樣地,使用 |
組態交易管理員
TransactionalTestExecutionListener
預期在測試的 Spring ApplicationContext
中定義 PlatformTransactionManager
bean。如果測試的 ApplicationContext
中有多個 PlatformTransactionManager
實例,您可以使用 @Transactional("myTxMgr")
或 @Transactional(transactionManager = "myTxMgr")
宣告限定詞,或者 TransactionManagementConfigurer
可以由 @Configuration
類別實作。請參閱 TestContextTransactionUtils.retrieveTransactionManager()
的 javadoc,以取得在測試的 ApplicationContext
中查閱交易管理員所用演算法的詳細資訊。
所有交易相關註解的示範
下列基於 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 Session 或 JPA 持續性 Context 狀態的應用程式碼時,請務必在執行該程式碼的測試方法中清除基礎工作單元。未能清除基礎工作單元可能會產生誤報:您的測試通過,但相同的程式碼在實際生產環境中擲回例外狀況。請注意,這適用於任何維護記憶體中工作單元的 ORM 框架。在下列基於 Hibernate 的範例測試案例中,一個方法示範了誤報,而另一個方法正確地公開了清除 Session 的結果
下列範例顯示了 JPA 的比對方法
|
測試 ORM 實體生命週期回呼
與關於避免 誤報 的注意事項類似,當測試 ORM 程式碼時,如果您的應用程式使用實體生命週期回呼 (也稱為實體監聽器),請務必在執行該程式碼的測試方法中清除基礎工作單元。未能清除或清空基礎工作單元可能會導致無法叫用某些生命週期回呼。 例如,當使用 JPA 時,除非在實體儲存或更新後叫用 下列範例示範如何清除
請參閱 Spring Framework 測試套件中的 JpaEntityListenerTests,以取得使用所有 JPA 生命週期回呼的工作範例。 |