几年前,我是为我的数据访问代码编写单元测试的那些开发人员之一。 我正在孤立地测试所有内容,我对自己感到非常满意。 老实说,我认为自己做得很好。 哦,男孩,我错了! 这篇博客文章描述了为什么我们不应该为数据访问代码编写单元测试,并解释为什么我们应该用集成测试代替单元测试。 让我们开始吧。

单元测试错误问题的答案

我们为数据访问代码编写测试,因为我们想知道它可以按预期工作。 换句话说,我们想找到这些问题的答案:

  1. 是否将正确的数据存储到使用的数据库?
  2. 我们的数据库查询是否返回正确的数据?

单元测试可以帮助我们找到想要的答案吗? 好吧, 单元测试的最基本规则之一是单元测试不应使用诸如数据库之类的外部系统 。 此规则不适用于当前情况,因为存储正确信息和返回正确查询结果的责任由我们的数据访问代码和使用的数据库划分。 例如,当我们的应用程序执行单个数据库查询时,职责划分如下:

  • 负责创建执行的数据库查询的数据访问代码。
  • 数据库负责执行数据库查询,并将查询结果返回给数据访问代码。

问题是,如果我们将数据访问代码与数据库隔离,则可以测试数据访问代码是否创建了“正确的”查询,但是我们无法确保所创建的查询返回正确的查询结果。 这就是为什么单元测试不能帮助我们找到想要的答案

告诫故事:假装是问题的一部分

有段时间我为数据访问代码编写了单元测试。 当时我有两个规则:

  1. 每段代码都必须单独进行测试。
  2. 让我们使用模拟。

我当时在一个使用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

编写数据访问代码测试–单元测试是浪费相关推荐

  1. 对编写的代码进行单元测试_编写数据访问代码测试–单元测试是浪费

    对编写的代码进行单元测试 几年前,我是为我的数据访问代码编写单元测试的那些开发人员之一. 我正在孤立地测试所有内容,我对自己感到非常满意. 老实说,我认为自己做得很好. 哦,男孩,我错了! 这篇博客文 ...

  2. junit编写测试代码_编写数据访问代码测试-不测试框架

    junit编写测试代码 当我们向数据访问代码编写测试时,是否应该测试其公共API的每种方法? 一开始听起来很自然. 毕竟,如果我们不测试所有内容,那么如何知道我们的代码可以按预期工作? 这个问题为我们 ...

  3. junit编写测试代码_编写数据访问代码测试–绿色建筑不够好

    junit编写测试代码 开始为数据访问代码编写集成测试之前,我们要做的第一件事是决定如何配置测试用例. 我们有两种选择:正确的一种和错误的一种. 不幸的是,许多开发人员选择错误. 我们如何避免犯同样的 ...

  4. 简化Redis数据访问代码RedisTemplate

    ---恢复内容开始--- Redis数据结构简介: Redis可以存储键与5中数据结构类型之间的映射,这5中数据结构类型分别是;String(字符串),List(列表),Set(集合),Hash(散列 ...

  5. [转自microsoft]NET 数据访问架构指南,-数据库连接的测试.即监视链接池化

    NET 数据访问架构指南 Alex Mackman, Chris Brooks, Steve Busby, 和 Ed Jezierski 微软公司 2001年10月 概述:本文提供了在多层.NET应用 ...

  6. .NET 数据访问架构指南(转)

    Alex Mackman, Chris Brooks, Steve Busby, 和 Ed Jezierski 微软公司 2001年10月 概述:本文提供了在多层.NET应用程序中实施基于ADO.NE ...

  7. .NET 数据访问架构指南

    Alex Mackman, Chris Brooks, Steve Busby, 和 Ed Jezierski 微软公司 2001年10月 概述:本文提供了在多层.NET应用程序中实施基于ADO.NE ...

  8. [转].NET 数据访问架构指南

    Microsoft   目录 ADO.NET简介 管理数据库链接 错误处理 性能 通过防火墙建立链接 处理 BLOBs 事务处理 数据分页 简介 如果你在为.NET应用程序设计数据访问层,那么就应该把 ...

  9. NET 数据访问架构指南

     发布者:[本站编辑]  来源:[]  浏览:[<script src="../../ArticleInfo.asp?action=viewtotal&ArticleId=82 ...

最新文章

  1. Latex中的插入表格
  2. 在桌面上创建一个宽带连接服务器,win7宽带连接怎么创建桌面
  3. Leetcode周赛5193. 删除字符使字符串变好
  4. C语言多种方法实现同一个功能
  5. UML小结以及基于领域模型的系统设计初步
  6. Hive belline提交命令Error: org.apache.thrift.transport.TTransportException: java.net.SocketException:
  7. (八)JS异步进阶,更深更广搞定JS异步【想要进大厂,更多异步的问题等着你】
  8. 工作151:初始登录样式
  9. 解决长email在表格td中不自动换行的问题 CSS强制不换行
  10. java检查变量是否定义_JavaScript检查变量是否存在(已定义/初始化)
  11. c语言main的性质,关于main()
  12. electron加载html加载不起来,Electron 预加载远程页面提升用户体验
  13. [渝粤教育] 郑州财税金融职业学院 玩转e时代 参考 资料
  14. setup_per_cpu_areas 函数
  15. AXURE RP EXTENSION FOR CHROME:AXURE RP铬延展剂--谷歌工具插件使用配置(打开并成功预览本地.html文件)
  16. [资讯]北京二套学区房奋斗目标
  17. IGBT芯片赛道竞争激烈,水光半导体Wassersun推出全新IGBT制程技术
  18. svn拉取文件失败_TortoiseSVN常见的错误信息与解决方法
  19. 动画师入门必读 迪士尼影响至今的十二条动画黄金法则
  20. halcon中的分水岭算法讲解以及作用和实例

热门文章

  1. 狂神说spring笔记
  2. android通讯录增删改查,android 通话记录的增删改查 .
  3. c语言程序为什么运行一半就自动关闭了,C语言为什么程序运行一半就出现停止工作...
  4. 转-HTTPClient调用https请求,通过基本认证用户名密码(Basic Auth)
  5. 转】Eclipse编辑Spring配置文件xml时自动提示类class包名
  6. maven 文件上传下载_使用Maven将文件上传和下载到S3
  7. java流式传输对象_使用Java 8在地图上流式传输
  8. 1.0jpa 2.0_JPA 2.1类型转换器–持久枚举的更好方法
  9. java8根据某个id删选_Java 8可选:如何使用它
  10. java8 默认方法_如何不使用Java 8默认方法