怎样编写测试类测试分支

如果我们的代码有明显的错误,我们很有动力对其进行改进。 但是,在某些时候,我们认为我们的代码“足够好”并继续前进。

通常,当我们认为改进现有代码的好处小于所需的工作时,就会发生这种情况。 当然,如果我们低估了投资回报,我们可能会打错电话,这会伤害我们。

这就是发生在我身上的事情,因此我决定写这篇文章,以便您避免犯同样的错误。

编写“良好”单元测试

如果我们要编写“好的”单元测试,则必须编写以下单元测试:

  • 只测试一件事 。 好的单元测试只能因一个原因而失败,并且只能断言一件事。
  • 被正确命名 。 测试方法的名称必须揭示测试失败的原因。
  • 模拟外部依赖关系(和状态) 。 如果单元测试失败,我们将确切知道问题出在哪里。

补充阅读:

  • 单元测试只能测试一件事情
  • 编写干净的测试:命名问题
  • 编写干净的测试:分而治之
  • 编写干净的测试:验证或不验证

如果我们编写满足这些条件的单元测试,我们将编写好的单元测试。 对?

我曾经这样认为。 现在我对此表示怀疑

善意铺平地狱之路

我从未见过一个决定编写糟糕的单元测试的软件开发人员。 如果开发人员正在编写单元测试,则他/她很有可能要编写好的单元测试。 但是,这并不意味着该开发人员编写的单元测试是好的。

我想编写既易于阅读又易于维护的单元测试。 我什至写了一个教程,描述了如何编写干净的测试 。 问题在于,本教程中给出的建议还不够好(尚未)。 它可以帮助我们入门,但是并没有显示出兔子洞的真正深度。

我的教程中描述的方法存在两个主要问题:

命名标准FTW?

如果我们使用Roy Osherove引入的“命名标准” ,我们注意到很难描述被测状态和预期行为。

当我们为简单场景编写测试时,此命名标准非常有效。 问题在于,真正的软件并不简单。 通常,我们最终使用以下两个选项之一来命名测试方法:

首先 ,如果我们尝试尽可能具体,那么我们的测试方法的方法名称就显得太过looooooooong。 最后,我们必须承认我们不能像我们想要的那样具体,因为方法名称会占用太多空间。

其次 ,如果我们尝试使方法名称尽可能短,则方法名称将不会真正描述测试状态和预期行为。

选择哪个选项并不重要,因为无论如何我们都会遇到以下问题:

  • 如果测试失败,则方法名称不一定表示要出错。 我们可以使用自定义断言来解决此问题,但是它们不是免费的。
  • 很难对我们的测试涵盖的场景进行简要概述。

这是我们在“ 编写干净测试”教程期间编写的测试方法的名称:

  • registerNewUserAccount_SocialSignInAndDuplicateEmail_ShouldThrowException()
  • registerNewUserAccount_SocialSignInAndDuplicateEmail_ShouldNotSaveNewUserAccount()
  • registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldSaveNewUserAccountAndSetSignInProvider()
  • registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldReturnCreatedUserAccount()
  • registerNewUserAccount_SocialSignInAnUniqueEmail_ShouldNotCreateEncodedPasswordForUser()

这些方法的名称不是很长,但是我们必须记住,编写这些单元测试是为了测试一种简单的注册方法。 当我使用这种命名约定为现实生活中的软件项目编写自动化测试时,最长的方法名称是我们最长的示例名称的两倍。

那不是很干净或可读。 我们可以做得更好

没有通用配置

在本教程中,我们使单元测试变得更好了 。 然而,他们仍然遭受这样的事实,即没有“自然的”方式在不同的单元测试之间共享配置。

这意味着我们的单元测试包含许多重复的代码,这些代码配置了我们的模拟对象并创建了在我们的单元测试中使用的其他对象。

同样,由于没有“自然”的方式表明某些常量仅与特定的测试方法相关,因此我们必须将所有常量添加到测试类的开头。

我们的测试类的源代码如下(突出显示有问题的代码):

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.security.crypto.password.PasswordEncoder;import static com.googlecode.catchexception.CatchException.catchException;
import static com.googlecode.catchexception.CatchException.caughtException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;@RunWith(MockitoJUnitRunner.class)
public class RepositoryUserServiceTest {private static final String REGISTRATION_EMAIL_ADDRESS = "john.smith@gmail.com";private static final String REGISTRATION_FIRST_NAME = "John";private static final String REGISTRATION_LAST_NAME = "Smith";private static final SocialMediaService SOCIAL_SIGN_IN_PROVIDER = SocialMediaService.TWITTER;private RepositoryUserService registrationService;@Mockprivate PasswordEncoder passwordEncoder;@Mockprivate UserRepository repository;@Beforepublic void setUp() {registrationService = new RepositoryUserService(passwordEncoder, repository);}@Testpublic void registerNewUserAccount_SocialSignInAndDuplicateEmail_ShouldThrowException() throws DuplicateEmailException {RegistrationForm registration = new RegistrationFormBuilder().email(REGISTRATION_EMAIL_ADDRESS).firstName(REGISTRATION_FIRST_NAME).lastName(REGISTRATION_LAST_NAME).isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER).build();when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(new User());catchException(registrationService).registerNewUserAccount(registration);assertThat(caughtException()).isExactlyInstanceOf(DuplicateEmailException.class);}@Testpublic void registerNewUserAccount_SocialSignInAndDuplicateEmail_ShouldNotSaveNewUserAccount() throws DuplicateEmailException {RegistrationForm registration = new RegistrationFormBuilder().email(REGISTRATION_EMAIL_ADDRESS).firstName(REGISTRATION_FIRST_NAME).lastName(REGISTRATION_LAST_NAME).isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER).build();when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(new User());catchException(registrationService).registerNewUserAccount(registration);verify(repository, never()).save(isA(User.class));}@Testpublic void registerNewUserAccount_SocialSignInAndUniqueEmail_
ShouldSaveNewUserAccountAndSetSignInProvider() throws DuplicateEmailException {RegistrationForm registration = new RegistrationFormBuilder().email(REGISTRATION_EMAIL_ADDRESS).firstName(REGISTRATION_FIRST_NAME).lastName(REGISTRATION_LAST_NAME).isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER).build();when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);registrationService.registerNewUserAccount(registration);ArgumentCaptor<User> userAccountArgument = ArgumentCaptor.forClass(User.class);verify(repository, times(1)).save(userAccountArgument.capture());User createdUserAccount = userAccountArgument.getValue();assertThatUser(createdUserAccount).hasEmail(REGISTRATION_EMAIL_ADDRESS).hasFirstName(REGISTRATION_FIRST_NAME).hasLastName(REGISTRATION_LAST_NAME).isRegisteredUser().isRegisteredByUsingSignInProvider(SOCIAL_SIGN_IN_PROVIDER);}@Testpublic void registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldReturnCreatedUserAccount() throws DuplicateEmailException {RegistrationForm registration = new RegistrationFormBuilder().email(REGISTRATION_EMAIL_ADDRESS).firstName(REGISTRATION_FIRST_NAME).lastName(REGISTRATION_LAST_NAME).isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER).build();when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);when(repository.save(isA(User.class))).thenAnswer(new Answer<User>() {@Overridepublic User answer(InvocationOnMock invocation) throws Throwable {Object[] arguments = invocation.getArguments();return (User) arguments[0];}});User createdUserAccount = registrationService.registerNewUserAccount(registration);assertThatUser(createdUserAccount).hasEmail(REGISTRATION_EMAIL_ADDRESS).hasFirstName(REGISTRATION_FIRST_NAME).hasLastName(REGISTRATION_LAST_NAME).isRegisteredUser().isRegisteredByUsingSignInProvider(SOCIAL_SIGN_IN_PROVIDER);}@Testpublic void registerNewUserAccount_SocialSignInAnUniqueEmail_ShouldNotCreateEncodedPasswordForUser() throws DuplicateEmailException {RegistrationForm registration = new RegistrationFormBuilder().email(REGISTRATION_EMAIL_ADDRESS).firstName(REGISTRATION_FIRST_NAME).lastName(REGISTRATION_LAST_NAME).isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER).build();when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);registrationService.registerNewUserAccount(registration);verifyZeroInteractions(passwordEncoder);}
}

一些开发人员认为看起来像上面示例的单元测试足够干净。 我理解这种情绪,因为我曾经是其中之一。 但是,这些单元测试有三个问题:

  1. 该案的实质并不尽如人意 。 因为每种测试方法在调用被测试方法并验证预期结果之前都会进行自我配置,所以我们的测试方法变得比必要的更长。 这意味着我们不能只看一眼随机测试方法并弄清楚它要测试什么。
  2. 编写新的单元测试很慢 。 因为每个单元测试都必须自行配置,所以向我们的测试套件中添加新的单元测试比它可能要慢得多。 另一个“意外”的缺点是,这种单元测试鼓励人们练习复制和粘贴编程 。
  3. 维持这些单元测试是一件痛苦的事 。 如果我们向注册表单添加新的必填字段,或者更改registerNewUserAccount()方法的实现,则必须对每个单元测试进行更改。 这些单元测试太脆弱了。

换句话说,这些单元测试很难阅读,很难编写和维护。 我们必须做得更好

摘要

这篇博客文章教会了我们四件事:

  • 即使我们认为我们正在编写好的单元测试,也不一定是正确的。
  • 如果由于必须更改许多单元测试而导致更改现有功能的速度很慢,那么我们就不会编写好的单元测试。
  • 如果添加新功能的速度很慢,因为我们必须向单元测试中添加大量重复的代码,那么我们就不会编写好的单元测试。
  • 如果我们看不到单元测试所涵盖的情况,那么我们就没有编写好的单元测试。

本教程的下一部分将回答这个非常相关的问题:

如果我们现有的单元测试很烂,我们该如何解决呢?

如果要编写干净的测试,则应阅读我的“ 编写干净的测试”教程 。

翻译自: https://www.javacodegeeks.com/2015/03/writing-clean-tests-trouble-in-paradise.html

怎样编写测试类测试分支

怎样编写测试类测试分支_编写干净的测试–天堂中的麻烦相关推荐

  1. Java编写学生类student程序_编写一个JAVA程序片断定义一个表示学生的类student.docx...

    编写一个JAVA程序片断 定义一个表示学生的类student,包括域"学号","班级","姓名","性别"," ...

  2. 怎样编写测试类测试分支_编写干净的测试-被认为有害的新内容

    怎样编写测试类测试分支 很难为干净的代码找到一个好的定义,因为我们每个人都有自己的单词clean的定义. 但是,有一个似乎是通用的定义: 简洁的代码易于阅读. 这可能会让您感到有些惊讶,但我认为该定义 ...

  3. 怎样编写测试类测试分支_编写干净的测试–从配置开始

    怎样编写测试类测试分支 很难为干净的代码找到一个好的定义,因为我们每个人都有自己的单词clean的定义. 但是,有一个似乎是通用的定义: 简洁的代码易于阅读. 这可能会让您感到有些惊讶,但我认为该定义 ...

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

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

  5. 编写干净的测试–天堂中的麻烦

    如果我们的代码有明显的错误,我们很有动力进行改进. 但是,在某些时候,我们认为我们的代码"足够好"并继续前进. 通常,当我们认为改进现有代码的好处小于所需的工作时,就会发生这种情况 ...

  6. java测试类写三角形_软件测试实验一——使用junit判断三角形

    一.简单描述下安装 junit, hamcrest and eclemma的过程 ①当然,有了eclipse软件,安装的过程会显得比较轻松 对于安装junit和hamcrest来说需要在官网(或者其它 ...

  7. java请编写公共类继承抽象类_(Java相关)怎么理解抽象类,继承和接口?

    著作权归作者所有. 商业转载请联系作者获得授权,非商业转载请注明出处. 作者:海子 来源:博客园 一.抽象类 在了解抽象类之前,先来了解一下抽象方法.抽象方法是一种特殊的方法:它只有声明,而没有具体的 ...

  8. 编写分段函数子函数_编写自己的函数

    编写分段函数子函数 PYTHON编程 (PYTHON PROGRAMMING) In Python, you can define your own functions. 在Python中,您可以定义 ...

  9. 监听器内加了测试类导致测试类无法运行:java.lang.ClassCastException: 测试类 cannot be cast to 监听器

    正常是可以运行的 如果加了测试类到监听类中,就报下面的错误 SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder&qu ...

最新文章

  1. [C++] NEW Advanced Usage
  2. 深入理解Objective-C:Category
  3. leetcode 135. Candy | 135. 分发糖果(原创图文详解,Java)
  4. NeuralFinder:集成人工生命和遗传算法自动发现神经网络最优结构
  5. Cloud for Customer手机应用上传图片禁止从本地相册选择图片的核心代码
  6. python自带的PIL库扩展图片大小给图片加上文字描述
  7. HDU2090 算菜价【水题】
  8. 3D Segmentation with Exponential LogarithmicLoss for Highly Unbalanced Object Sizes-MICCAI2018【论文理解】
  9. Linux内核Makefile编译生成内核目标文件的过程
  10. 坐标的先对转化clienttoscreen与screentosclient
  11. Java 序列化漏洞多到修不完
  12. python自动化输入文本_快速掌握Python Selenium Web自动化:)四、使用Selenium在网页上进行操作...
  13. 《华为研发》阅读 - 16 (矩阵式管理)
  14. 磊科Netcore路由器无线无缝中继(桥接)设置方法
  15. 记一下chrome浏览器被毒霸域名劫持解决方案
  16. 【CLR】程序集查找与GAC
  17. php: RGB 转 HSB(HSV)
  18. 交互技术前沿学习分享——利用眼动追踪改良广告界面
  19. div展开和折叠 php,超酷堆叠图片展开和折叠
  20. MyTest.axf section `.text‘ will not fit in region `Flash30‘

热门文章

  1. AT3949-[AGC022D]Shopping【贪心】
  2. bzoj3482,jzoj3238-超时空旅行hiperprostor【最短路,凸包,斜率优化】
  3. 动态规划训练15 [Monkey and Banana HDU - 1069 ]
  4. 11、mysql数据表中数据的查询(3)
  5. Sentinel(五)之流量控制
  6. Oracle入门(十四)之PL/SQL
  7. 彻底理解正向代理和反向代理
  8. java.sql.SQLException: The server time zone value '�й���׼ʱ��' is unrecognized
  9. java 泛型参数的类型_Java获得泛型参数类型
  10. vue插槽面试题_VUE面试题解析,半年出一篇,建议收藏!