事務管理
在 TestContext 框架中,事務由 TransactionalTestExecutionListener
管理,它是預設配置的,即使你沒有在測試類上顯式宣告 @TestExecutionListeners
。然而,要啟用事務支援,你必須在透過 @ContextConfiguration
語義載入的 ApplicationContext
中配置一個 PlatformTransactionManager
bean(更多細節稍後提供)。此外,你必須在測試的類級別或方法級別宣告 Spring 的 @Transactional
註解。
測試管理的事務
測試管理的事務是透過 TransactionalTestExecutionListener
宣告式管理的事務,或者透過 TestTransaction
程式設計式管理的事務(稍後描述)。你不應將這類事務與 Spring 管理的事務(由 Spring 在載入用於測試的 ApplicationContext
中直接管理)或應用管理的事務(在被測試呼叫的應用程式碼中程式設計式管理)混淆。Spring 管理的事務和應用管理的事務通常會參與到測試管理的事務中。然而,如果 Spring 管理的事務或應用管理的事務配置的傳播型別不是 REQUIRED
或 SUPPORTS
,則應謹慎使用(有關詳情,請參見事務傳播的討論)。
搶佔式超時和測試管理的事務
在使用測試框架中的任何形式的搶佔式超時與 Spring 的測試管理的事務結合時,必須謹慎。 具體來說,Spring 的測試支援在呼叫當前測試方法*之前*,會將事務狀態繫結到當前執行緒(透過 可能發生這種情況的情況包括但不限於以下幾種。
|
啟用和停用事務
使用 @Transactional
註解測試方法會導致測試在事務中執行,該事務預設在測試完成後自動回滾。如果測試類使用 @Transactional
註解,則該類層次結構中的每個測試方法都在一個事務中執行。未註解 @Transactional
(在類或方法級別)的測試方法不在事務中執行。請注意,@Transactional
不支援測試生命週期方法——例如,註解了 JUnit Jupiter 的 @BeforeAll
、@BeforeEach
等方法。此外,註解了 @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
中的靜態方法來程式設計式地與測試管理的事務進行互動。例如,你可以在測試方法、前置方法和後置方法中使用 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
會確保你的事務前方法或事務後方法在適當的時間執行。
一般來說, 然而,自 Spring Framework 6.1 起,對於使用 JUnit Jupiter 結合
|
任何前置方法(例如,註解了 JUnit Jupiter 的 類似地,註解了 |
配置事務管理器
TransactionalTestExecutionListener
要求在測試的 Spring ApplicationContext
中定義一個 PlatformTransactionManager
bean。如果在測試的 ApplicationContext
中有多個 PlatformTransactionManager
例項,你可以透過使用 @Transactional(\"myTxMgr\")
或 @Transactional(transactionManager = \"myTxMgr\")
宣告限定符,或者由一個 @Configuration
類實現 TransactionManagementConfigurer
。有關在測試的 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 Framework 測試套件中的JpaEntityListenerTests。 |