自从我编写第一个单元测试以来已经有10年了。 从那时起,我不记得我已经编写了成千上万的单元测试。 老实说,我在源代码和测试代码之间没有任何区别。 对我来说是同一回事。 测试代码是源代码的一部分。 在过去的3-4年中,我与多个开发团队合作,并且有机会查看了大量的测试代码。 在这篇文章中,我总结了经验不足的开发人员在编写单元测试时通常会犯的最常见错误。

让我们看一下以下简单的类示例,该类收集注册数据,对其进行验证并执行用户注册。 显然,该方法非常简单,其目的是演示单元测试的常见错误,而不是提供功能齐全的注册示例:

public class RegistrationForm {private String name,email,pwd,pwdVerification;// Setters - Getters are ommitted public boolean register(){validate();return doRegister();}private void validate () {check(name, "email");check(email, "email");check(pwd, "email");check(pwdVerification, "email");if (!email.contains("@")) {throw new ValidationException(name + " cannot be empty.");} if ( !pwd.equals(pwdVerification))throw new ValidationException("Passwords do not match.");}private void check(String value, String name) throws ValidationException {if ( value == null) {throw new ValidationException(name + " cannot be empty.");}if (value.length() == 0) {throw new ValidationException(name + " is too short.");}}private boolean doRegister() {//Do something with the persistent contextreturn true;}

这是注册方法的相应单元测试,有意显示单元测试中最常见的错误。 实际上,我已经看过很多次非常相似的测试代码,所以这不是我所说的科幻小说:

@Testpublic void test_register(){RegistrationForm form = new RegistrationForm();form.setEmail("Al.Pacino@example.com");form.setName("Al Pacino");form.setPwd("GodFather");form.setPwdVerification("GodFather");assertNotNull(form.getEmail());assertNotNull(form.getName());assertNotNull(form.getPwd());assertNotNull(form.getPwdVerification());form.register();}

现在,此测试显然将通过,开发人员将看到绿灯,所以竖起大拇指! 让我们转到下一个方法。 但是,此测试代码有几个重要问题。

在我的拙见中,第一个是单元测试的最大误用是测试代码没有充分测试寄存器方法。 实际上,它仅测试许多可能路径中的一个。 我们确定该方法将正确处理空参数吗? 如果电子邮件中不包含@字符或密码不匹配,该方法将如何工作? 开发人员倾向于只为成功的路径编写单元测试,而我的经验表明,代码中发现的大多数错误都与成功的路径无关。 一个非常好的规则要记住的是,对于每一个方法,你需要N个测试,其中N等于在圈复杂度将所有私有方法调用的圈复杂度的方法。

接下来是测试方法的名称。 为此,我部分归咎于所有这些现代IDE,它们自动为测试方法(如示例中的方法)生成愚蠢的名称。 测试方法的命名应向读者解释将要测试的内容和条件 。 换句话说,它应该描述正在测试的路径。 在我们的情况下,更好的名称可以是: should_register_when_all_registration_data_are_valid。 在本文中,您可以找到几种命名单元测试的方法,但是对我来说,“应该”模式最接近人类语言,并且在阅读测试代码时更容易理解。

现在,让我们看一下代码的内容。 有几个断言,这违反了每个测试方法应断言一件事的规则 。 此声明四(4)个RegistrationForm属性的状态。 这使测试更难以维护和阅读(哦,是的,测试代码应该像源代码一样可维护和可读。请记住,对我而言它们之间没有区别),并且很难理解测试的哪一部分失败。

此测试代码还声明了setter / getter。 这真的有必要吗? 为了回答这个问题,我将引用罗伊·奥什罗夫(Roy Osherove)的名言:“ 单元测试的艺术 ”

属性(Java中的获取器/设置器)是很好的示例代码,通常不包含任何逻辑,并且不需要测试。 但是要当心:在属性中添加任何检查后,您将要确保逻辑已经过测试。

在我们的案例中,设置器/获取器中没有业务逻辑,因此这些断言完全没有用。 此外,他们错了,因为他们甚至没有测试安装员的正确性。 想象一下,一个邪恶的开发人员将getEmail方法的代码更改为始终返回常量String而不是email属性值。 该测试仍将通过,因为它断言setter不为null,并且未断言期望值。 因此,这可能是您要记住的一条规则。 断言方法的返回值时,请始终尝试尽可能具体 。 换句话说,除非您不关心实际的返回值,否则请尽量避免使用assertIsNull,assertIsNotNull。

我们正在查看的测试代码的最后但并非最不重要的问题是,从未断言正在测试的实际方法( 寄存器 )。 它在测试方法内部被调用,但是我们从不评估其结果。 这种反模式的变化甚至更糟。 在测试用例中甚至不会调用被测方法。 因此,请记住, 您不仅应调用被测方法,而且还应始终声明预期结果,即使它只是一个布尔值 。 有人可能会问:“无效方法是什么?”。 好的问题,但这是另一次讨论–可能是另一篇文章,但是为您提供一些提示,测试void方法可能会掩盖不好的设计,或者应该使用验证方法调用的框架(例如Mockito.Verify )来完成

作为奖励,您应该记住这是一条最终规则。 想象一下, doRegister实际上已实现,并且对外部数据库做了一些实际的工作。 如果某个本地环境中未安装数据库的开发人员尝试运行测试,将会发生什么情况。 正确! 一切都会失败。 确保测试即使从仅可访问代码和JDK的最笨拙的终端运行,也将具有相同的行为 。 没有网络,没有服务,没有数据库,没有文件系统。 没有!

翻译自: https://www.javacodegeeks.com/2014/09/common-mistakes-junior-developers-do-when-writing-unit-tests.html

初级开发人员在编写单元测试时常犯的错误相关推荐

  1. 初级测试开发面试题_初级开发人员在编写单元测试时常犯的错误

    初级测试开发面试题 自从我编写第一个单元测试以来已经有10年了. 从那时起,我不记得我已经编写了成千上万的单元测试. 老实说,我在源代码和测试代码之间没有任何区别. 对我来说是同一回事. 测试代码是源 ...

  2. 初级开发人员的缺点_如何避免我作为初级开发人员犯的这7个错误

    初级开发人员的缺点 Beginning your career as a junior developer can be scary. There will be many unknown chall ...

  3. java开发简历编写_如何通过几个简单的步骤编写出色的初级开发人员简历

    java开发简历编写 So you've seen your dream junior developer role advertised, and are thinking about applyi ...

  4. 汉堡菜单_开发人员在编写汉堡菜单时犯的错误

    汉堡菜单 by Jared Tong 汤杰(Jared Tong) 开发人员在编写汉堡菜单时犯的错误 (The mistake developers make when coding a hambur ...

  5. 开发人员在编写 HTML 和 CSS 时最常犯的六大错误

    生活中犯错误是正常的,没有人不会犯错误,更何况是开发人员呢?今天我们就来卡看看开发人员在编写 HTML 和 CSS 时最常犯的六大错误有哪些. 作者 | Stas Melnikov 译者 | 弯月,责 ...

  6. java开发第一天上班_从第一天开始,如何成为一名优秀的团队合作伙伴,成为初级开发人员

    java开发第一天上班 One of the many things you might be asking yourself when starting your software developm ...

  7. 初级开发人员的缺点_在您作为初级开发人员的第一年获得此建议

    初级开发人员的缺点 Are you a junior developer embarking on your software development career? 您是从事软件开发事业的初级开发人 ...

  8. [译] 如何成为一名优秀的初级开发人员

    本文翻译自Medium上的一篇文章,原文标题:How To Be A Good Junior Developer. 点击文末的阅读原文可以跳转到英文原文. 到明年,我的职业编程生涯即将满15年.15年 ...

  9. 成为杰出人物的路线图_如何成为杰出的初级开发人员

    成为杰出人物的路线图 If you're reading this, you probably just got your first tech job - so congratulations! G ...

最新文章

  1. MP实战系列(二)之集成swagger
  2. TRex 学习 (4) ---- stateful ( advanced )
  3. 如何使用Jekyll+GitHub Pages搭建个人博客站点
  4. nametuple用法(转)
  5. IronRuby - 文件编码惹的祸
  6. C++ 异常 与 ”为什么析构函数不能抛出异常“ 问题
  7. mac securecrt程序无响应_如何重置mac上的系统管理控制器smc教程
  8. unity怎么制作云飘动_Unity 如何制作星空粒子效果?
  9. Hadoop2.x介绍与源代码编译
  10. CSS魔法堂:小结一下Box Model与Positioning Scheme
  11. druid 连接池的释放 配合上spring bean销毁_spring boot基于DRUID数据源密码加密及数据源监控实现...
  12. 有什么做电音的软件?3款好用App让你爱上唱歌
  13. dcs world f15c教学_教师成长 | PBL在幼儿英文绘本教学中的应用
  14. Visio里如何画树状图?
  15. Oracle数据库岗位,Oracle数据库岗位职责
  16. 计算机应用 一级学科,一级学科、二级学科,考研专业哪个包含了计算机考研方向...
  17. 矩形波的傅里叶级数及代码
  18. 关于富文本编辑器的图片处理
  19. 律师登台痛批,BSA置身事外
  20. 漫话算法[回溯]:从《大话西游》到掌握回溯思想!

热门文章

  1. 新闻发布项目——分页公共类(PageUitl )
  2. html5中 save方法,如何HTML5画布另存为窗口8 Metro应用中的图像文件?(How to save html5 c...
  3. 用python进行自然语言处理_Python自然语言处理示例:SVM和贝叶斯分类
  4. js 时间戳转换成时间_JavaScript 时间戳转成日期格式
  5. docker 打包镜像_Spring Boot2 系列教程(四十一)部署 Spring Boot 到远程 Docker 容器
  6. buildpack_使用Buildpack容器化Spring Boot应用程序
  7. rest web服务_在WildFly的REST Web服务中与Jackson的双向关系
  8. lucene 源码分析_Lucene分析过程指南
  9. junit rule_使用@Rule在JUnit中测试文件和目录
  10. 从fork-join /线程池调用的Singelton bean中的访问spring请求范围缓存