编写数据访问代码测试–单元测试是浪费
幾年前,我是為我的數據訪問代碼編寫單元測試的那些開發人員之一。 我正在孤立地測試所有內容,我對自己感到非常滿意。 老實說,我認為自己做得很好。 哦,男孩,我錯了! 這篇博客文章描述了為什么我們不應該為數據訪問代碼編寫單元測試,并解釋為什么我們應該用集成測試代替單元測試。 讓我們開始吧。
單元測試錯誤問題的答案
我們為數據訪問代碼編寫測試,因為我們想知道它可以按預期工作。 換句話說,我們想找到這些問題的答案:
單元測試可以幫助我們找到想要的答案嗎? 好吧, 單元測試的最基本規則之一是單元測試不應使用諸如數據庫之類的外部系統 。 此規則不適用于當前情況,因為存儲正確信息和返回正確查詢結果的責任由我們的數據訪問代碼和使用的數據庫劃分。 例如,當我們的應用程序執行單個數據庫查詢時,職責劃分如下:
- 負責創建執行的數據庫查詢的數據訪問代碼。
- 數據庫負責執行數據庫查詢,并將查詢結果返回給數據訪問代碼。
問題是,如果我們將數據訪問代碼與數據庫隔離,則可以測試數據訪問代碼是否創建了“正確的”查詢,但是我們無法確保所創建的查詢返回正確的查詢結果。 這就是為什么單元測試不能幫助我們找到想要的答案 。
告誡故事:假裝是問題的一部分
有段時間我為數據訪問代碼編寫了單元測試。 當時我有兩個規則:
我當時在一個使用Spring Data JPA的項目中工作,而動態查詢是通過使用JPA條件查詢構建的。 如果您不熟悉Spring Data JPA,則可能需要閱讀Spring Data JPA教程的第四部分,該教程介紹了如何使用Spring Data JPA創建JPA條件查詢 。 無論如何,我創建了一個規范構建器類來構建Specification <Person>對象。 創建Specification <Person>對象后,將其轉發給我的Spring Data JPA存儲庫,該存儲庫執行查詢并返回查詢結果。 規范構建器類的源代碼如下所示:
import org.springframework.data.jpa.domain.Specification;import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root;public class PersonSpecifications {public static Specification<Person> lastNameIsLike(final String searchTerm) {return new Specification<Person>() {@Overridepublic Predicate toPredicate(Root<Person> personRoot, CriteriaQuery<?> query, CriteriaBuilder cb) {String likePattern = getLikePattern(searchTerm); return cb.like(cb.lower(personRoot.<String>get(Person_.lastName)), likePattern);}private String getLikePattern(final String searchTerm) {return searchTerm.toLowerCase() + "%";}};} }讓我們看一下“驗證”規范構建器類創建“正確”查詢的測試代碼。 請記住,我是按照自己的規則編寫此測試類的,這意味著結果應該很棒。 PersonSpecificationsTest類的源代碼如下所示:
import org.junit.Before; import org.junit.Test; import org.springframework.data.jpa.domain.Specification;import javax.persistence.criteria.*;import static junit.framework.Assert.assertEquals; import static org.mockito.Mockito.*;public class PersonSpecificationsTest {private static final String SEARCH_TERM = "Foo";private static final String SEARCH_TERM_LIKE_PATTERN = "foo%";private CriteriaBuilder criteriaBuilderMock;private CriteriaQuery criteriaQueryMock;private Root<Person> personRootMock;@Beforepublic void setUp() {criteriaBuilderMock = mock(CriteriaBuilder.class);criteriaQueryMock = mock(CriteriaQuery.class);personRootMock = mock(Root.class);}@Testpublic void lastNameIsLike() {Path lastNamePathMock = mock(Path.class); when(personRootMock.get(Person_.lastName)).thenReturn(lastNamePathMock);Expression lastNameToLowerExpressionMock = mock(Expression.class);when(criteriaBuilderMock.lower(lastNamePathMock)).thenReturn(lastNameToLowerExpressionMock);Predicate lastNameIsLikePredicateMock = mock(Predicate.class);when(criteriaBuilderMock.like(lastNameToLowerExpressionMock, SEARCH_TERM_LIKE_PATTERN)).thenReturn(lastNameIsLikePredicateMock);Specification<Person> actual = PersonSpecifications.lastNameIsLike(SEARCH_TERM);Predicate actualPredicate = actual.toPredicate(personRootMock, criteriaQueryMock, criteriaBuilderMock);verify(personRootMock, times(1)).get(Person_.lastName);verifyNoMoreInteractions(personRootMock);verify(criteriaBuilderMock, times(1)).lower(lastNamePathMock);verify(criteriaBuilderMock, times(1)).like(lastNameToLowerExpressionMock, SEARCH_TERM_LIKE_PATTERN);verifyNoMoreInteractions(criteriaBuilderMock);verifyZeroInteractions(criteriaQueryMock, lastNamePathMock, lastNameIsLikePredicateMock);assertEquals(lastNameIsLikePredicateMock, actualPredicate);} }這有意義嗎? 沒有! 我必須承認,此測試對任何人都沒有價值,應該盡快刪除。 該測試存在三個主要問題:
- 它不能幫助我們確保數據庫查詢返回正確的結果。
- 很難理解并使情況更糟,它描述了查詢的構建方式,但沒有描述查詢應返回的內容。
- 這樣的測試很難編寫和維護。
事實是,此單元測試是不應編寫的測試的教科書示例。 它對我們沒有任何價值,但我們仍然必須維護它。 因此, 這是浪費! 但是,如果我們為數據訪問代碼編寫單元測試,就會發生這種情況。 我們最終得到了一個測試套件,無法測試正確的東西。
數據訪問測試正確完成
我是單元測試的忠實擁護者,但是在某些情況下,它并不是工作的最佳工具。 這是其中一種情況。 數據訪問代碼與使用的數據存儲有非常密切的關系。 這種關系是如此緊密,以至于沒有數據存儲,數據訪問代碼本身就沒有用。 這就是為什么將我們的數據訪問代碼與使用的數據存儲區分開來是沒有意義的。 解決這個問題很簡單。 如果我們要為數據訪問代碼編寫全面的測試,則必須將數據訪問代碼與使用的數據存儲一起進行測試。 這意味著我們必須忘記單元測試并開始編寫集成測試 。 我們必須了解,只有集成測試才能驗證
- 我們的數據訪問代碼創建正確的數據庫查詢。
- 我們的數據庫返回正確的查詢結果。
如果您想知道如何編寫針對Spring支持的存儲庫的集成測試,則應閱讀我的博客文章“ Spring Data JPA教程:集成測試” 。 它描述了如何為Spring Data JPA存儲庫編寫集成測試。 但是,在為使用關系數據庫的任何存儲庫編寫集成測試時,可以使用相同的技術。 例如, 為測試“ 將jOOQ與Spring結合使用”教程中的示例應用程序而編寫的集成測試使用該博客文章中描述的技術。
摘要
這篇博客文章教會了我們兩件事:
- 我們了解到,單元測試無法幫助我們驗證數據訪問代碼是否正常運行,因為我們無法確保將正確的數據插入到數據存儲中或查詢返回正確的結果。
- 我們了解到,應該使用集成測試來測試數據訪問代碼,因為數據訪問代碼與使用的數據存儲之間的關系是如此緊密,以至于沒有必要將它們分開。
只剩下一個問題:您是否還在為數據訪問代碼編寫單元測試?
翻譯自: https://www.javacodegeeks.com/2014/07/writing-tests-for-data-access-code-unit-tests-are-waste.html
總結
以上是生活随笔為你收集整理的编写数据访问代码测试–单元测试是浪费的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Counterpoint:2023 年
- 下一篇: 为什么我不信任通配符,以及为什么我们仍然