[英]What's the best strategy for unit-testing database-driven applications?
我使用很多Web應用程序,這些應用程序由后端不同復雜程度的數據庫驅動。 通常,存在與業務和表示邏輯分離的ORM層。 這使得對業務邏輯的單元測試相當簡單; 事物可以在離散模塊中實現,測試所需的任何數據都可以通過對象模擬來偽造。
但是測試ORM和數據庫本身一直充滿了問題和妥協。
多年來,我嘗試了一些策略,其中沒有一個完全滿足我。
使用已知數據加載測試數據庫。 針對ORM運行測試並確認正確的數據返回。 這里的缺點是您的測試數據庫必須跟上應用程序數據庫中的任何模式更改,並且可能會不同步。 它還依賴於人工數據,並且可能不會暴露由於愚蠢的用戶輸入而發生的錯誤。 最后,如果測試數據庫很小,它將不會顯示缺失索引等低效率。 (好吧,最后一個不是真的應該使用單元測試,但它不會受到傷害。)
加載生產數據庫的副本並對其進行測試。 這里的問題是你可能不知道在任何給定時間生產數據庫中有什么; 如果數據隨時間變化,您的測試可能需要重寫。
有些人指出,這兩種策略都依賴於特定的數據,單元測試應該只測試功能。 為此,我見過建議:
您使用了哪些策略來測試數據庫驅動的應用程序? 什么對你有用?
我實際上已經使用了你的第一種方法取得了相當大的成功,但我認為這種解決方案可以解決一些問題:
保留整個架構和腳本,以便在源代碼管理中創建它,以便任何人都可以在簽出后創建當前數據庫架構。 此外,將樣本數據保存在部分構建過程中加載的數據文件中。 當您發現導致錯誤的數據時,請將其添加到示例數據中以檢查錯誤是否重新出現。
使用持續集成服務器構建數據庫模式,加載示例數據並運行測試。 這就是我們如何使測試數據庫保持同步(在每次測試運行時重建它)。 雖然這要求CI服務器具有對其自己的專用數據庫實例的訪問權和所有權,但我說每天建立3次我們的數據庫模式可以極大地幫助找到在交付之前可能找不到的錯誤(如果不是以后的話) )。 我不能說我在每次提交之前重建架構。 有人嗎? 通過這種方法你不必(也許我們應該,但如果有人忘記,這不是什么大問題)。
對於我的組,用戶輸入在應用程序級別(而不是db)完成,因此通過標准單元測試進行測試。
加載生產數據庫副本:
這是我上一份工作中使用的方法。 這是幾個問題的巨大痛苦原因:
模擬數據庫服務器:
我們目前的工作也是這樣做的。 在每次提交之后,我們對注入了模擬db訪問器的應用程序代碼執行單元測試。 然后我們每天三次執行上面描述的完整db構建。 我絕對推薦這兩種方法。
由於以下原因,我總是針對內存數據庫(HSQLDB或Derby)運行測試:
一旦測試開始,內存數據庫就會加載新數據,在大多數測試之后,我調用ROLLBACK來保持穩定。 始終保持測試數據庫中的數據穩定! 如果數據一直在變化,則無法進行測試。
數據從SQL,模板DB或轉儲/備份加載。 如果它們是可讀格式,我更喜歡轉儲,因為我可以將它們放在VCS中。 如果這不起作用,我使用CSV文件或XML。 如果我必須加載大量數據......我沒有。 您永遠不必加載大量數據:)不適用於單元測試。 性能測試是另一個問題,適用不同的規則。
我一直在問這個問題很長一段時間,但我認為沒有靈丹妙葯。
我目前所做的是模擬DAO對象並保持內存表示一個良好的對象集合,這些對象代表可以存在於數據庫中的有趣數據案例。
我用這種方法看到的主要問題是,你只覆蓋與DAO層交互的代碼,但從不測試DAO本身,根據我的經驗,我發現在該層上也發生了很多錯誤。 我還保留了一些針對數據庫運行的單元測試(為了在本地使用TDD或快速測試),但這些測試永遠不會在我的持續集成服務器上運行,因為我們沒有為此目的保留數據庫而且我認為在CI服務器上運行的測試應該是自包含的。
我覺得非常有趣的另一種方法,但並不總是值得花費一點時間,就是在單元測試中運行的嵌入式數據庫上創建用於生產的相同模式。
即使毫無疑問,這種方法可以提高您的覆蓋率,但也有一些缺點,因為您必須盡可能接近ANSI SQL,以使其適用於您當前的DBMS和嵌入式替換。
無論你認為什么與你的代碼更相關,有一些項目可以使它更容易,如DbUnit 。
即使有一些工具允許你以某種方式模擬你的數據庫(例如jOOQ的MockConnection
,這可以在這個答案中看到 - 免責聲明,我為jOOQ的供應商工作),我建議不要模擬更大的數據庫復雜的查詢。
即使您只是想集成測試您的ORM,請注意ORM向您的數據庫發出一系列非常復雜的查詢,這些查詢可能會有所不同。
模擬所有這些以生成合理的虛擬數據是非常困難的,除非您實際在mock中構建一個小數據庫,它解釋傳輸的SQL語句。 話雖如此,使用一個眾所周知的集成測試數據庫,您可以使用眾所周知的數據輕松重置,您可以使用它來運行集成測試。
我使用第一個(對測試數據庫運行代碼)。 我看到你用這種方法提出的唯一實質性問題是模式失去同步的可能性,我通過在我的數據庫中保留版本號並通過應用每個版本增量的更改的腳本進行所有模式更改來處理。
我還首先針對我的測試環境進行所有更改(包括數據庫模式),因此它最終成為另一種方式:在所有測試通過后,將模式更新應用於生產主機。 我還在我的開發系統上保留了一對單獨的測試與應用程序數據庫,這樣我就可以在觸摸真實生產框之前驗證數據庫升級是否正常工作。
我正在使用第一種方法,但有點不同,可以解決您提到的問題。
運行DAO測試所需的一切都在源代碼管理中。 它包括用於創建數據庫的模式和腳本(docker非常適合這種情況)。 如果可以使用嵌入式DB - 我用它來提高速度。
與其他描述的方法的重要區別在於,測試所需的數據不是從SQL腳本或XML文件加載的。 除了一些有效常量的字典數據外,所有內容都是由應用程序使用實用程序函數/類創建的。
主要目的是使測試使用的數據
它基本上意味着這些實用程序允許在測試本身中以聲明方式僅指定測試所必需的內容,並省略不相關的內容。
為了讓這意味着什么在實踐中的一些想法,可以考慮對一些DAO與工程測試Comment
s到Post
S按寫Authors
。 為了測試這種DAO的CRUD操作,應該在DB中創建一些數據。 測試看起來像:
@Test
public void savedCommentCanBeRead() {
// Builder is needed to declaratively specify the entity with all attributes relevant
// for this specific test
// Missing attributes are generated with reasonable values
// factory's responsibility is to create entity (and all entities required by it
// in our example Author) in the DB
Post post = factory.create(PostBuilder.post());
Comment comment = CommentBuilder.comment().forPost(post).build();
sut.save(comment);
Comment savedComment = sut.get(comment.getId());
// this checks fields that are directly stored
assertThat(saveComment, fieldwiseEqualTo(comment));
// if there are some fields that are generated during save check them separately
assertThat(saveComment.getGeneratedField(), equalTo(expectedValue));
}
與SQL腳本或帶有測試數據的XML文件相比,這有幾個優點:
我發現測試在執行時會提交更方便。 首先,如果提交從未發生,則無法檢查某些效果(例如DEFERRED CONSTRAINTS
)。 其次,當測試失敗時,可以在DB中檢查數據,因為它不會被回滾恢復。
因此,這有一個缺點,即測試可能會產生損壞的數據,這將導致其他測試失敗。 為了解決這個問題,我嘗試隔離測試。 在上面的示例中,每個測試都可以創建新的Author
並且創建與其相關的所有其他實體,因此很少發生沖突。 為了處理可能被破壞但不能表示為DB級別約束的剩余不變量,我使用一些編程檢查可以在每次單個測試之后運行的錯誤條件(並且它們在CI中運行但通常在本地關閉以獲得性能原因)。
對於基於JDBC的項目(直接或間接,例如JPA,EJB,...),您可以模擬不是整個數據庫(在這種情況下,最好在真正的RDBMS上使用測試數據庫),但只能在JDBC級別進行模型化。
優勢是這樣的抽象,因為JDBC數據(結果集,更新計數,警告......)與后端無關:您的prod db,測試數據庫,或者只是為每個測試提供的一些模型數據案件。
通過針對每種情況模擬的JDBC連接,無需管理測試數據庫(清理,只需要一次測試,重新加載固件,......)。 每個模型連接都是隔離的,無需清理。 每個測試用例中只提供最少的必需夾具來模擬JDBC交換,這有助於避免管理整個測試數據庫的復雜性。
Acolyte是我的框架,其中包括用於此類模型的JDBC驅動程序和實用程序: http : //acolyte.eu.org 。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.