SpringBoot單元測試之數(shù)據(jù)隔離詳解
前言
我們在寫單元測試時,有一個比較重要的要求是可以重復運行,即只要外部參數(shù)不變,那么一定可以獲取確定的結(jié)果。 那么這樣就會有一個比較麻煩的問題:數(shù)據(jù)污染。 以數(shù)據(jù)庫操作為例,對于一些查詢類的測試用例倒還好,因為只要保證數(shù)據(jù)存在,那么查詢操作天生就是冪等的,不管你查多少次,數(shù)據(jù)都不會變。 但是對于寫入操作就不友好了,每一次運行單元測試,都會在數(shù)據(jù)庫中產(chǎn)生新的數(shù)據(jù),可能在第一次運行時正常,第二次運行時就由于數(shù)據(jù)沖突導致失敗。那對于這種情況,我們應該如何去解決?
下面分享兩種我使用過的數(shù)據(jù)隔離的方式,供大家參考。
數(shù)據(jù)隔離
測試事務
這種方式比較常規(guī),適用場景也比較廣泛,即我們在測試用例中,通過@Transactional
注解開始事務,此時整個單元測試方法會被包裹在事務中,并且會在運行結(jié)束后,自動回滾。
我們可以在類或方法上添加@Transactional
注解
/** * 在類上添加 @Transactional 注解,可以讓測試方法在執(zhí)行完畢后自動回滾,不會對數(shù)據(jù)庫造成影響。 * 如果在方法上添加,則只影響對應的方法 */ @SpringBootTest @Transactional public class TransactionalTests extends AbstractTransactionalJUnit4SpringContextTests { @Resource private UserService userService; @Test void createRecord() { User user = userService.createUser(); //to other things } }
或者繼承AbstractTransactionalJUnit4SpringContextTests
類
/** * 在類上添加 @Transactional 注解,可以讓測試方法在執(zhí)行完畢后自動回滾,不會對數(shù)據(jù)庫造成影響。 * 如果在方法上添加,則只影響對應的方法 */ @SpringBootTest public class TransactionalTests extends AbstractTransactionalJUnit4SpringContextTests { @Resource private UserService userService; @Test void createRecord() { User user = userService.createUser(); //to other things } }
AbstractTransactionalJUnit4SpringContextTests
實際上也是添加了@Transactional
注解,只不過在此之外,它還提供了一些JDBC接口
,可以讓我們更方便的操作數(shù)據(jù)庫。
使用這種方式來實現(xiàn)單元測試的話,由于測試用例中的所有數(shù)據(jù)庫操作都在事務中進行,然后運行結(jié)束后事務回滾,就可以保證不會染污數(shù)據(jù)庫數(shù)據(jù),做到數(shù)據(jù)隔離。但是它也存在一些問題:
- 影響單元測試結(jié)果,在方法上層添加事務,本質(zhì)上還是改變了方法的邏輯。比如說我被測試的方法中用到了不同的數(shù)據(jù)源(主從數(shù)據(jù)庫),一但被包裹了事務,那么意味著我在這個事務內(nèi),都是使用同一條連接,這會對我們實際上期望運行的邏輯生產(chǎn)影響。
- 排查問題困難,因為在單元測試運行結(jié)束之后,事務最終會回滾,也就意味著我們DB最終是沒有數(shù)據(jù)的,很難排查問題。
數(shù)據(jù)預處理與清理
上面說到事務會存在一些問題,那么我們不使用事務,怎么保證數(shù)據(jù)隔離?我們可以通過幾種方式來在單元測試運行前后手動的增加或清理數(shù)據(jù):
通過@Sql
注解來定義單元測試前后需要執(zhí)行的SQL腳本
@Sql( scripts = "create-data.sql", config = @SqlConfig(transactionMode = ISOLATED) ) @Sql( scripts = "delete-data.sql", config = @SqlConfig(transactionMode = ISOLATED), executionPhase = AFTER_TEST_METHOD ) @Test void createRecord() { User user = userService.createUser(); //to other things }
我們通過@Sql
注解定義指定了兩個SQL腳本,分別用于在運行單元測試前創(chuàng)建測試數(shù)據(jù),以及在運行結(jié)束后,刪除數(shù)據(jù),這樣就可以保證我們的每次運行單元測試時的數(shù)據(jù)都是隔離的。
@Sql只是一種方式,我們還可以通過其它各種方式來執(zhí)行,比如@BeforeEach、@AfterEach來定義單元測試執(zhí)行前后的攔截器,或者Junit的@ExtendWith來定義外部的監(jiān)聽器等各種方式來處理數(shù)據(jù)的預處理與清理。
總結(jié)
上面分享了兩種數(shù)據(jù)隔離的方式,一般情況下都可以滿足我們的單元測試需求,但是實際上兩種方式都還存在一些問題,比如不管是哪種方式,都依賴于外部的數(shù)據(jù)庫
,如果我們依賴的數(shù)據(jù)庫出現(xiàn)了異常,也會影響到我們的單元測試運行。而且使用外部數(shù)據(jù)庫也是無法完全做到數(shù)據(jù)隔離的,還是會有數(shù)據(jù)沖突的風險
。那還有沒有更好的方法? 如果大家的單元測試環(huán)境有docker環(huán)境的話,那么可以考慮引入Testcontainers
,可以很好的解決這個問題。下篇文章我會詳細講一下Testcontainers
在單測數(shù)據(jù)隔離中的實踐。
以上就是SpringBoot單元測試之數(shù)據(jù)隔離詳解的詳細內(nèi)容,更多關(guān)于SpringBoot單元測試的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java輸入學號、姓名、年齡并對其進行輸出的實現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于Java輸入學號、姓名、年齡并對其進行輸出的實現(xiàn)方法,在計算機編程中,輸出學號和姓名是一個常見的任務,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2023-09-09springboot themaleaf 第一次進頁面不加載css的問題
這篇文章主要介紹了springboot themaleaf 第一次進頁面不加載css的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10idea中創(chuàng)建jsp項目的詳細實戰(zhàn)步驟
才學javaWeb,以防自己忘記創(chuàng)建項目的過程,所以淺淺的記錄一下吧,下面這篇文章主要給大家介紹了關(guān)于idea中創(chuàng)建jsp項目的詳細步驟,文中通過圖文介紹的非常詳細,需要的朋友可以參考下2022-09-09Java數(shù)據(jù)結(jié)構(gòu)之有向圖的拓撲排序詳解
這篇文章主要為大家詳細介紹了Java數(shù)據(jù)結(jié)構(gòu)中有向圖的拓撲排序,文中的示例代碼講解詳細,具有一定的借鑒價值,感興趣的小伙伴可以了解一下2022-11-11