不要仅仅依靠单元测试
当您构建一个复杂的系统时,仅仅测试组件是不够的。 这很关键,但还不够。 想象一下一家汽车厂生产并进口最高质量的零件,但组装好之后再也不会启动发动机了。 如果您的测试用例套件几乎不包含单元测试,则您将永远无法确保系统整体正常运行。 让我们举一个人为的例子:
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
不要仅仅依靠单元测试相关推荐
- 近似线性依靠matlab_不要仅仅依靠单元测试
近似线性依靠matlab 当您构建一个复杂的系统时,仅仅测试组件是不够的. 这很关键,但还不够. 想象一下一家汽车厂生产和进口最高质量的零件,但组装好汽车后再也没有启动发动机. 如果您的测试用例套件几 ...
- 单元测试实践思考(junit5+jmockit+testcontainer)
文章目录 背景 方案设计 单元测试指导思想 单层隔离 内部穿透 技术实现 依赖管理 基础架构 封装Junit5&Jmockit 单元测试配置 TestContainer封装 官方方案 实际方案 ...
- ASP.NET Core 中做集成测试的三种方案
学习·进步 老张的哲学 不定期更新的 日常 在平时的开发中,我们很少会关注到测试的问题,更别说集成测试了,除非是公司有硬性要求或者是自己的开源项目中,为了整体架构的完整性,需要用测试来做辅助点缀,而更 ...
- CaseStudy-数据缓存出错
这个星期负责的项目出现了一次线上的故障,发现的很偶然,要不后边很可能会时不时地让人纠结一段时间,同时还不容易定位和解决.在这里把这次事故记录下来,引以为戒吧. 1.问题描述 在项目的服务器端中用了一个 ...
- 借助xUnit减少了生产问题
目录 介绍 背景 使用代码 兴趣点 考虑到使用xUnit编写许多测试的方式,需要改进两件事:测试需要更加真实,并应防止日志记录受限.使测试更切合实际,将减少生产问题的可能性.此外,测试日志记录的内容可 ...
- SpringBoot系列: Eclipse+Maven环境准备
这个链接比我写得更全面, http://tengj.top/2018/01/01/maven/ ============================= 20190115补充: maven 的一些插 ...
- Spring基础学习(一)
Spring基础 Spring概述 Spring是框架是一个轻量级的企业级开发的一站式解决方案.所谓解决方案就是可以基于Spring解决JavaEE开发的所有问题.Spring框架主要提供了IOC容器 ...
- 石家庄铁道大学 2016 上半年软件工程课助教总结
通常,总结会相对整个过程短不少.然而,因为过程的内容本就很多,所以需要总结的东西似乎就有不少,所以这篇总结计划分成三个部分: 关于<构建之法> 关于软工课和同学们 关于我自己 关于< ...
- iOS 大型项目开发漫谈
从http://www.cocoachina.com/ios/20150828/13170.html转载,谢谢写这篇文章的大神! 标题有些吓人请不要害怕,不过这确实不是扫盲贴,需要一定的iOS开发基础 ...
最新文章
- c语言分段错误空指针,C语言空指针总结 - 祂的小哥哥的个人空间 - OSCHINA - 中文开源技术交流社区...
- Tensorflow— word2vec
- linux 挂载光盘映像,在 Windows Mac和Linux上,如何挂载iso和其他光盘映像
- php网站制作商品结算怎么做,一种以让产品、信息快速同步多网站销售并结算的技术的制作方法...
- HAProxy高并发问题解决
- 无法加载 DLL“SQLite.Interop.DLL”: 找不到指定的模块。 (异常来自 HRESULT:0x8007007E)。...
- 很多人不知道的中国高校“V9联盟”,另一领域的顶尖牛校!
- 【SIGIR 2021 最佳学生论文】图像文本检索的动态模态交互建模
- linux下C语言简单实现线程池
- layui 行变灰_layui table设置某一行的字体颜色方法
- 用汇编的眼光看C++(之算术符重载陷阱)
- JavaScript函数setInterval()和setTimeout()正确的写法
- 线路规划实现用java_北京地铁出行线路规划系统项目总结(Java+Flask+Vue实现)
- MacBook 电脑Touch Bar该怎么设置
- 微博长图快速排版生成工具
- [转载]刘峰获“区块链60人”2020赋能中国区块链创新人物奖
- ubuntu中安装卸载mysql8.0及修改密码
- 初学MSP430F5529时钟以及FLL配置
- Linux——匿名管道、命名管道及进程池概念和实现原理
- HTML5 Canvas火焰效果 像火球发射一样
热门文章
- java包 类 方法_Java中包与包之间方法的调用及其关键字区分(基础)
- 随机数发生器怎么用_用随机数发生器射击自己的脚
- cobertura_Cobertura和Sonar 5.1的问题
- openshift学习_在OpenShift上将JMS与JBoss A-MQ结合使用。 学习了有关远程客户端和加密的经验。...
- matchers依赖_Hamcrest Matchers的高级创建
- netbeans6.8_NetBeans 8.0的五个新性能提示
- spring期刊状态_无状态Spring安全性第2部分:无状态认证
- JDK 15中的确切绝对整数
- Kubernetes集群上的Apache Ignite和Spring第3部分:测试应用程序
- avro文件导入到hive_XML到Avro的转换