測試後如何拆除或清空 HSQLDB 資料庫
1. 概述
在編寫整合測試時,在測試之間維護乾淨的資料庫狀態對於確保可重現的結果並避免意外的副作用至關重要。 HSQLDB(HyperSQL 資料庫)是一種輕量級記憶體資料庫,由於其速度快且簡單,經常用於測試目的。
在本文中,我們將探討在測試運行後在 Spring Boot 中拆除或清空 HSQLDB 資料庫的各種方法。雖然我們的重點是 HSQLDB,但這些概念也廣泛適用於其他資料庫類型。
對於測試隔離,我們使用了@DirtiesContext
註釋,因為我們正在模擬不同的方法,並且我們希望確保每種方法都經過隔離測試,以避免它們之間產生任何副作用。
2. 方法
我們將探索可用於拆除 HSQLDB 資料庫的五種方法,即:
- 使用 Spring 的
@Transactional
註解進行事務管理 - 應用程式屬性配置
- 使用
JdbcTemplate
執行查詢 - 使用
JdbcTestUtils
進行清理 - 自訂 SQL 腳本
2.1.使用@Transactional
註解進行事務管理
當測試使用@Transactional
註解時,Spring 的事務管理會被啟動。這意味著每個測試方法都在自己的交易中運行,並自動回滾測試期間所做的任何更改:
@SpringBootTest
@ActiveProfiles("hsql")
@Transactional
@DirtiesContext
public class TransactionalIntegrationTest {
@Autowired
private CustomerRepository customerRepository;
@Autowired
private JdbcTemplate jdbcTemplate;
@BeforeEach
void setUp() {
String insertSql = "INSERT INTO customers (name, email) VALUES (?, ?)";
Customer customer = new Customer("John", "[email protected]");
jdbcTemplate.update(insertSql, customer.getName(), customer.getEmail());
}
@Test
void givenCustomer_whenSaved_thenReturnSameCustomer() throws Exception {
Customer customer = new Customer("Doe", "[email protected]");
Customer savedCustomer = customerRepository.save(customer);
Assertions.assertEquals(customer, savedCustomer);
}
// ... integration tests
}
此方法快速且高效,因為它不需要手動清理。
2.2.應用程式屬性配置
在此方法中,我們有一個簡單的設置,我們將首選選項指派給屬性spring.jpa.hibernate.ddl-auto
。有不同的可用值,但create
和create-drop
通常對於清理最有用。
create
選項在會話開始時刪除並重新建立模式,而**create-drop
在開始時建立模式並在結束時刪除它**。
在我們的程式碼範例中,我們為每個測試方法添加了@DirtiesContext
註釋,以確保每個測試都會觸發模式建立和刪除過程。然而,能力越大,責任越大;我們必須明智地使用這些選項,否則我們可能會意外地清除我們的生產資料庫:
@SpringBootTest
@ActiveProfiles("hsql")
@TestPropertySource(properties = { "spring.jpa.hibernate.ddl-auto=create-drop" })
public class PropertiesIntegrationTest {
@Autowired
private CustomerRepository customerRepository;
@Autowired
private JdbcTemplate jdbcTemplate;
@BeforeEach
void setUp() {
String insertSql = "INSERT INTO customers (name, email) VALUES (?, ?)";
Customer customer = new Customer("John", "[email protected]");
jdbcTemplate.update(insertSql, customer.getName(), customer.getEmail());
}
@Test
@DirtiesContext
void givenCustomer_whenSaved_thenReturnSameCustomer() throws Exception {
Customer customer = new Customer("Doe", "[email protected]");
Customer savedCustomer = customerRepository.save(customer);
Assertions.assertEquals(customer, savedCustomer);
}
// ... integration tests
}
這種方法的優點是配置簡單,不需要在測試類別中自訂程式碼,對於需要持久性資料的測試特別有用。但是,與涉及自訂程式碼的其他方法相比,它提供的細粒度控制較少。
我們可以在測試類別中設定此屬性,也可以將其分配給整個配置文件,但我們必須小心,因為它會影響同一配置文件中的所有測試,這可能並不總是可取的。
2.3.使用JdbcTemplate
執行查詢
在這個方法中,我們使用JdbcTemplate
API,它允許我們執行各種查詢。具體來說,我們可以使用它在每次測試後或在所有測試後立即截斷表。這種方法簡單直接,我們只需要編寫查詢,並且它保留了表結構:
@SpringBootTest
@ActiveProfiles("hsql")
@DirtiesContext
class JdbcTemplateIntegrationTest {
@Autowired
private CustomerRepository customerRepository;
@Autowired
private JdbcTemplate jdbcTemplate;
@AfterEach
void tearDown() {
this.jdbcTemplate.execute("TRUNCATE TABLE customers");
}
@Test
void givenCustomer_whenSaved_thenReturnSameCustomer() throws Exception {
Customer customer = new Customer("Doe", "[email protected]");
Customer savedCustomer = customerRepository.save(customer);
Assertions.assertEquals(customer, savedCustomer);
}
// ... integration tests
}
然而,這種方法需要手動列出或編寫所有表的查詢,並且在處理大量表時可能會比較慢。
2.4.使用JdbcTestUtils
進行清理
在這個方法中,Spring Boot 框架中的JdbcTestUtils
類別提供了對資料庫清理過程的更多控制。它允許我們執行任意SQL語句來刪除特定表中的資料。
雖然它與JdbcTemplate
類似,但存在關鍵差異,特別是在語法簡單性方面,因為一行程式碼可以處理多個表。它也很靈活,但僅限於從表中刪除所有行:
@SpringBootTest
@ActiveProfiles("hsql")
@DirtiesContext
public class JdbcTestUtilsIntegrationTest {
@Autowired
private CustomerRepository customerRepository;
@Autowired
private JdbcTemplate jdbcTemplate;
@AfterEach
void tearDown() {
JdbcTestUtils.deleteFromTables(jdbcTemplate, "customers");
}
@Test
void givenCustomer_whenSaved_thenReturnSameCustomer() throws Exception {
Customer customer = new Customer("Doe", "[email protected]");
Customer savedCustomer = customerRepository.save(customer);
Assertions.assertEquals(customer, savedCustomer);
}
// ... integration tests
}
然而,這種方法有其缺點。與截斷相比,大型資料庫的刪除操作速度較慢,而且它不處理引用完整性約束,這可能會導致具有外鍵關係的資料庫出現問題。
2.5.自訂 SQL 腳本
此方法使用 SQL 腳本來清理資料庫,這對於更複雜的設定特別有用。它既靈活又可自訂,因為它可以處理複雜的清理場景,同時保持資料庫邏輯與測試程式碼分離。
在 Spring Boot 3 中,SQL 腳本可以放置在類別級別,在每個方法或所有測試方法之後執行。例如,讓我們看一下配置它的一種方法:
@SpringBootTest
@ActiveProfiles("hsql")
@DirtiesContext
@Sql(scripts = "/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public class SqlScriptIntegrationTest {
@Autowired
private CustomerRepository customerRepository;
@Autowired
private JdbcTemplate jdbcTemplate;
@BeforeEach
void setUp() {
String insertSql = "INSERT INTO customers (name, email) VALUES (?, ?)";
Customer customer = new Customer("John", "[email protected]");
jdbcTemplate.update(insertSql, customer.getName(), customer.getEmail());
}
@Test
void givenCustomer_whenSaved_thenReturnSameCustomer() throws Exception {
Customer customer = new Customer("Doe", "[email protected]");
Customer savedCustomer = customerRepository.save(customer);
Assertions.assertEquals(customer, savedCustomer);
}
它也可以應用於單一測試方法,如下例所示,我們將其放在最後一個方法上以在所有測試後進行清理:
@Test
@Sql(scripts = "/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
void givenExistingCustomer_whenFoundByName_thenReturnCustomer() throws Exception {
// ... integration test
}
然而,這種方法確實需要維護單獨的 SQL 文件,這對於更簡單的用例來說可能有點過分了。
三、結論
每次測試後拆除資料庫是軟體測試的基本概念。透過遵循本文中概述的方法,我們可以確保我們的測試保持隔離並在每次運行時產生可靠的結果。
雖然我們在這裡討論的每種方法都有其自身的優點和缺點,但選擇最適合我們專案特定要求的方法非常重要。
與往常一樣,完整的程式碼以及本文中提供的所有範例都可以在 GitHub 上找到。