当您构建一个复杂的系统时,仅仅测试组件是不够的。 这很关键,但还不够。 想象一下一家汽车厂生产并进口最高质量的零件,但组装好之后再也不会启动发动机了。 如果您的测试用例套件几乎不包含单元测试,则您将永远无法确保系统整体正常运行。 让我们举一个人为的例子:

public class UserDao {public List<User> findRecentUsers() {try {return //run some query} catch(EmptyResultDataAccessException ignored) {return null;}}//...
}

我希望您已经在catch块中发现了一个反模式(而且我并不是想忽略异常,这似乎是可以预期的)。 作为好公民,我们决定通过以下方式解决问题: 返回一个空集合,而不是null

public class UserDao {public List<User> findRecentUsers() {try {return //run some query} catch(EmptyResultDataAccessException ignored) {return Collections.emptyList();}}//...
}

修复非常简单,我们几乎忘记了运行单元测试,但是以防万一我们执行它们并发现第一个失败的测试用例:

public class UserDaoTest {private UserDao userDao;@Beforepublic void setUp() throws Exception {userDao = new UserDao();}@Testpublic void shouldReturnNullWhenNoRecentUsers() throws Exception {//given//whenfinal List<User> result = userDao.findRecentUsers();//thenassertThat(result).isNull();}@Testpublic void shouldReturnOneRecentUser() throws Exception {//givenfinal User lastUser = new User();userDao.storeLoginEvent(lastUser);//whenfinal List<User> result = userDao.findRecentUsers();//thenassertThat(result).containsExactly(lastUser);}@Testpublic void shouldReturnTwoRecentUsers() throws Exception {//givenfinal User lastUser = new User();final User oneButLastUser = new User();userDao.storeLoginEvent(oneButLastUser);userDao.storeLoginEvent(lastUser);//whenfinal List<User> result = userDao.findRecentUsers();//thenassertThat(result).containsExactly(lastUser, oneButLastUser);}}

显然,不仅代码被破坏了(通过返回null而不是像null的空集合),而且还进行了一项测试来验证这种虚假行为。 我很确定测试是在实现之后编写的,并且必须以某种方式处理现实。 在没有实施特性的事先知识的情况下,没有人会编写这样的测试。 因此,我们修复了测试,并乐意等待绿色CI的建立–最终来了。 几天后,我们的应用程序在生产中因NullPointerException中断。 它打破了经过彻底的单元测试的地方:

public class StatService {private final UserDao userDao;public StatService(UserDao userDao) {this.userDao = userDao;}public void welcomeMostRecentUser() {final List<User> recentUsers = userDao.findRecentUsers();if (recentUsers != null) {welcome(recentUsers.get(0));}}private void welcome(User user) {//...}
}

我们很惊讶,因为此类已被单元测试完全覆盖(为清楚起见,省略了验证步骤):

@RunWith(MockitoJUnitRunner.class)
public class WelcomeServiceTest {@Mockprivate UserDao userDaoMock;private WelcomeService welcomeService;@Beforepublic void setup() {welcomeService = new WelcomeService(userDaoMock);}@Testpublic void shouldNotSendWelcomeMessageIfNoRecentUsers() throws Exception {//givengiven(userDaoMock.findRecentUsers()).willReturn(null);//whenwelcomeService.welcomeMostRecentUser();//then//verify no message sent}@Testpublic void shouldSendWelcomeMessageToMostRecentUser() throws Exception {//givengiven(userDaoMock.findRecentUsers()).willReturn(asList(new User()));//whenwelcomeService.welcomeMostRecentUser();//then//verify user welcomed}//...}

您知道问题出在哪里吗? 我们更改了UserDao类的合同,同时使它在表面上“看起来”相同。 通过修复损坏的测试,我们认为它仍然可以工作。 但是, WelcomeService仍然依赖UserDao的旧行为,该行为要么返回null ,要么返回具有至少一个元素的列表。 使用模拟框架记录了此行为,因此我们能够对单元中的WelcomeService进行单独测试。 换句话说,我们无法确保这两个组件仍然可以正常工作,我们仅对它们进行了单独测试。 回到我们的汽车隐喻–所有零件仍然可以放在一起(相同的合同),但是其中一个内部的行为与以前不同。 那么,到底出了什么问题? 这里至少存在四个问题,如果缓解了任何一个,这些都不会发生。

首先, UserDao的作者未能认识到返回null而空列表似乎更加直观。 这引出了一个问题:有没有之间的差异显著null和空集? 如果是,也许您正在尝试在单个返回值中“编码”太多信息? 如果没有,为什么还要增加API使用者的生活呢? 遍历空集合不需要任何额外的工作; 对可能为null collection进行迭代需要一个额外的条件。 WelcomeService作者也因假定null表示空集合而失败。 他应该解决丑陋的API,而不要依赖它。 在这种情况下,他本可以使用CollectionUtils.isNotEmpty()并更具防御性:

if (CollectionUtils.isNotEmpty(recentUsers)) {

对于更全面的解决方案,他还可以考虑装饰 UserDao并将null替换为空collection。 甚至使用AOP在整个应用程序中全局修复此类API。 顺便说一句,这也适用于String 。 在99%的情况下, null ,空字符串和很少有空格的字符串之间没有“业务”差异。 除非您真的想区分它们,否则默认情况下使用StringUtils.isBlank()或类似名称。

最终,“修复” UserDao的人看不到大图。 仅仅修复单元测试是不够的。 当您在不更改API的情况下更改类的行为时(这对于动态语言尤为重要),您很可能会错过使用该API的地方,从而失去上下文。 但是最大的失败是缺少组件/系统测试 。 如果仅使用一个同时运行WelcomeService UserDao ,就会发现此错误。 仅有100%的代码覆盖率是不够的。 您测试了拼图的每一个部分,但从未看过完成的图片。 至少进行一些较大的烟雾测试。 否则,您将不再具有如此强大的信心,即当测试呈绿色时,代码就可以使用了。

参考: 不要单靠我们的JCG合作伙伴 Tomasz Nurkiewicz的NoBlogDefFound博客进行单元测试 。

翻译自: https://www.javacodegeeks.com/2013/02/dont-rely-on-unit-tests-alone.html

不要仅仅依靠单元测试相关推荐

  1. 近似线性依靠matlab_不要仅仅依靠单元测试

    近似线性依靠matlab 当您构建一个复杂的系统时,仅仅测试组件是不够的. 这很关键,但还不够. 想象一下一家汽车厂生产和进口最高质量的零件,但组装好汽车后再也没有启动发动机. 如果您的测试用例套件几 ...

  2. 单元测试实践思考(junit5+jmockit+testcontainer)

    文章目录 背景 方案设计 单元测试指导思想 单层隔离 内部穿透 技术实现 依赖管理 基础架构 封装Junit5&Jmockit 单元测试配置 TestContainer封装 官方方案 实际方案 ...

  3. ASP.NET Core 中做集成测试的三种方案

    学习·进步 老张的哲学 不定期更新的 日常 在平时的开发中,我们很少会关注到测试的问题,更别说集成测试了,除非是公司有硬性要求或者是自己的开源项目中,为了整体架构的完整性,需要用测试来做辅助点缀,而更 ...

  4. CaseStudy-数据缓存出错

    这个星期负责的项目出现了一次线上的故障,发现的很偶然,要不后边很可能会时不时地让人纠结一段时间,同时还不容易定位和解决.在这里把这次事故记录下来,引以为戒吧. 1.问题描述 在项目的服务器端中用了一个 ...

  5. 借助xUnit减少了生产问题

    目录 介绍 背景 使用代码 兴趣点 考虑到使用xUnit编写许多测试的方式,需要改进两件事:测试需要更加真实,并应防止日志记录受限.使测试更切合实际,将减少生产问题的可能性.此外,测试日志记录的内容可 ...

  6. SpringBoot系列: Eclipse+Maven环境准备

    这个链接比我写得更全面, http://tengj.top/2018/01/01/maven/ ============================= 20190115补充: maven 的一些插 ...

  7. Spring基础学习(一)

    Spring基础 Spring概述 Spring是框架是一个轻量级的企业级开发的一站式解决方案.所谓解决方案就是可以基于Spring解决JavaEE开发的所有问题.Spring框架主要提供了IOC容器 ...

  8. 石家庄铁道大学 2016 上半年软件工程课助教总结

    通常,总结会相对整个过程短不少.然而,因为过程的内容本就很多,所以需要总结的东西似乎就有不少,所以这篇总结计划分成三个部分: 关于<构建之法> 关于软工课和同学们 关于我自己 关于< ...

  9. iOS 大型项目开发漫谈

    从http://www.cocoachina.com/ios/20150828/13170.html转载,谢谢写这篇文章的大神! 标题有些吓人请不要害怕,不过这确实不是扫盲贴,需要一定的iOS开发基础 ...

最新文章

  1. c语言分段错误空指针,C语言空指针总结 - 祂的小哥哥的个人空间 - OSCHINA - 中文开源技术交流社区...
  2. Tensorflow— word2vec
  3. linux 挂载光盘映像,在 Windows Mac和Linux上,如何挂载iso和其他光盘映像
  4. php网站制作商品结算怎么做,一种以让产品、信息快速同步多网站销售并结算的技术的制作方法...
  5. HAProxy高并发问题解决
  6. 无法加载 DLL“SQLite.Interop.DLL”: 找不到指定的模块。 (异常来自 HRESULT:0x8007007E)。...
  7. 很多人不知道的中国高校“V9联盟”,另一领域的顶尖牛校!
  8. 【SIGIR 2021 最佳学生论文】图像文本检索的动态模态交互建模
  9. linux下C语言简单实现线程池
  10. layui 行变灰_layui table设置某一行的字体颜色方法
  11. 用汇编的眼光看C++(之算术符重载陷阱)
  12. JavaScript函数setInterval()和setTimeout()正确的写法
  13. 线路规划实现用java_北京地铁出行线路规划系统项目总结(Java+Flask+Vue实现)
  14. MacBook 电脑Touch Bar该怎么设置
  15. 微博长图快速排版生成工具
  16. [转载]刘峰获“区块链60人”2020赋能中国区块链创新人物奖
  17. ubuntu中安装卸载mysql8.0及修改密码
  18. 初学MSP430F5529时钟以及FLL配置
  19. Linux——匿名管道、命名管道及进程池概念和实现原理
  20. HTML5 Canvas火焰效果 像火球发射一样

热门文章

  1. java包 类 方法_Java中包与包之间方法的调用及其关键字区分(基础)
  2. 随机数发生器怎么用_用随机数发生器射击自己的脚
  3. cobertura_Cobertura和Sonar 5.1的问题
  4. openshift学习_在OpenShift上将JMS与JBoss A-MQ结合使用。 学习了有关远程客户端和加密的经验。...
  5. matchers依赖_Hamcrest Matchers的高级创建
  6. netbeans6.8_NetBeans 8.0的五个新性能提示
  7. spring期刊状态_无状态Spring安全性第2部分:无状态认证
  8. JDK 15中的确切绝对整数
  9. Kubernetes集群上的Apache Ignite和Spring第3部分:测试应用程序
  10. avro文件导入到hive_XML到Avro的转换