分而治之_编写干净的测试–分而治之

分而治之

好的单元测试应该仅出于一个原因而失败。 这意味着适当的单元测试仅测试一个逻辑概念。

如果我们要编写干净的测试,则必须识别那些逻辑概念,并且每个逻辑概念只编写一个测试用例。

这篇博客文章描述了我们如何识别从测试中发现的逻辑概念,以及如何将现有的单元测试分成多个单元测试。

干净还不够好

让我们先看一下单元测试的源代码,该源代码确保当使用唯一的电子邮件地址和社交登录提供者创建新用户帐户时, RepositoryUserService类的registerNewUserAccount(RegistrationForm userAccountData)方法能够按预期工作。

该单元测试的源代码如下所示:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
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 org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
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_SocialSignInAndUniqueEmail_ShouldCreateNewUserAccountAndSetSignInProvider() 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);assertThat(createdUserAccount).hasEmail(REGISTRATION_EMAIL_ADDRESS).hasFirstName(REGISTRATION_FIRST_NAME).hasLastName(REGISTRATION_LAST_NAME).isRegisteredUser().isRegisteredByUsingSignInProvider(SOCIAL_SIGN_IN_PROVIDER);verify(repository, times(1)).findByEmail(REGISTRATION_EMAIL_ADDRESS);verify(repository, times(1)).save(createdUserAccount);verifyNoMoreInteractions(repository);verifyZeroInteractions(passwordEncoder);}
}

这个单元测试非常干净。 毕竟,我们的测试类,测试方法以及在测试方法内部创建的局部变量具有描述性名称。 我们还用常数替换了幻数,并创建了特定领域的语言来创建新对象和编写断言。

但是, 我们可以使这项测试更好

此单元测试的问题在于它可能出于多种原因而失败。 如果发生以下情况,它将失败:

  1. 我们的服务方法不会检查是否从我们的数据库中找不到输入到注册表中的电子邮件地址。
  2. 持久化的User对象的信息与在注册表中输入的信息不匹配。
  3. 返回的User对象的信息不正确。
  4. 我们的服务方法通过使用PasswordEncoder对象为用户创建密码。

换句话说,此单元测试测试了四个不同的逻辑概念,这导致以下问题:

  • 如果此测试失败,我们不一定知道为什么失败。 这意味着我们必须阅读单元测试的源代码。
  • 单元测试有点长,这使得阅读起来有些困难。
  • 很难描述预期的行为。 这意味着很难为我们的测试方法找到好名字。

通过确定单元测试将失败的情况,我们可以确定单个单元测试所涵盖的逻辑概念。

这就是为什么我们需要将此测试分为四个单元测试。

一测试,一故障

下一步是将单元测试分成四个新的单元测试,并确保每个单元测试都测试一个逻辑概念。 我们可以通过编写以下单元测试来做到这一点:

  1. 我们需要确保我们的服务方法检查用户提供的电子邮件地址是否唯一。
  2. 我们需要验证持久性User对象的信息是否正确。
  3. 我们需要确保返回的User对象的信息正确。
  4. 我们需要验证我们的服务方法没有为使用社交登录提供商的用户创建编码密码。

编写完这些单元测试后,测试类的源代码如下所示:

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 net.petrikainulainen.spring.social.signinmvc.user.model.UserAssert.assertThat;
import static org.mockito.Matchers.isA;
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_SocialSignInAndUniqueEmail_ShouldCheckThatEmailIsUnique() 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);verify(repository, times(1)).findByEmail(REGISTRATION_EMAIL_ADDRESS);}@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();assertThat(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);assertThat(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);}
}

编写只测试一个逻辑概念的单元测试的明显好处是,很容易知道为什么测试失败。 但是,此方法还有其他两个好处:

  • 指定期望的行为很容易。 这意味着更容易为我们的测试方法找出好名字。
  • 因为这些单元测试比原始单元测试要短得多,所以更容易弄清测试方法/组件的要求。 这有助于我们将测试转换为可执行规范。

让我们继续并总结从这篇博客文章中学到的知识。

摘要

现在,我们已经成功地将单元测试分为四个较小的单元测试,它们测试了一个逻辑概念。 这篇博客文章教会了我们两件事:

  • 我们了解到,通过确定测试失败的情况,我们可以确定单个单元测试所涵盖的逻辑概念。
  • 我们了解到,编写仅测试一个逻辑概念的单元测试有助于我们将测试用例编写成可执行的规范,从而确定测试方法/组件的要求。

翻译自: https://www.javacodegeeks.com/2014/06/writing-clean-tests-divide-and-conquer.html

分而治之

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/342165.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[渝粤教育] 长沙航空职业技术学院 液压与气动技术 参考 资料

教育 -液压与气动技术-章节资料考试资料-长沙航空职业技术学院【】 随堂测-液压传动原理 1、【单选题】液压传动是以液体的&#xff08; &#xff09;能来传递动力的。 A、化学能 B、动能 C、势能 D、压力能 参考资料【 】 2、【单选题】液压传动是以&#xff08; &#xff09;…

万兆交换机与千兆交换机的区别有哪些?

交换机的发展经历了百兆、千兆&#xff0c;随着广大用户对数据传输要求的提高&#xff0c;因此有了现如今万兆交换机。百兆交换机正在逐步退出舞台&#xff0c;但是千兆交换机与万兆交换机的使用情况不相上下&#xff0c;那么&#xff0c;在什么情况下我们应该选择千兆交换机&a…

酒店电视方案 酒店建设高清数字电视系统的解决方案

近年来&#xff0c;随着互联网的发展&#xff0c;数字电视系统已经成为趋势。酒店作为旅游休闲的重要场所&#xff0c;为宾客提供高清的电视信号是非常有必要的。数字电视接入系统是宾客了解酒店资讯、观看电视电影、进行娱乐放松等的重要载体&#xff0c;其电视信号的质量对于…

将Spring Boot应用程序与Amazon Cognito集成

在本文中&#xff0c;我们将展示如何使用Spring Security 5.0中引入的OAuth 2.0客户端库 &#xff0c;在Spring Boot应用程序中为身份验证用户使用Amazon Cognito服务。 什么是AWS Cognito&#xff1f; Amazon Cognito是由AWS提供的服务&#xff0c;除了提供对访问AWS服务的授…

【渝粤教育】国家开放大学2018年春季 7402-22T社会问题 参考试题

试卷编号&#xff1a;7402 座位号 2017——2018学年度第二学期期末考试 社会问题 试题 2018年7月 答题框&#xff1a; 1&#xff0e;社会问题研究的&#xff08; &#xff09;功能是回答社会问题是怎样的。 A、描述性研究 B、解释性研究 C、预测性研究 D、规范性研究 2&#…

网管型工业以太网交换机的几种管理方式

网管型工业交换机按其字面上的意思就是可以网络管理的交换机&#xff0c;管理方式有三种&#xff0c;可通过串口管理、可通过Web管理、通过网管软件管理&#xff0c;提供了基于终端控制口(Console)、基于Web页面以及支持Telnet远程登录网络等多种网络管理方式。因此网络管理人员…

【渝粤教育】国家开放大学2018年秋季 0359-21T会计学原理 参考试题

试卷编号&#xff1a;0390 座位号 2018—2019学年度第一学期期末考试 古代诗歌散文专题试题 2019年1月 1&#xff0e;在四言诗的发展史上&#xff0c; 是一个具有转折意义的人物&#xff0c;他突破了《诗经》的格局&#xff0c;为四言诗注入了新的活力。 2&#xff0e; 是屈原…

有线电视的现状与发展,全国一网与广电5G一体化建设

2020年&#xff0c;中宣部印发《全国有线电视网络整合发展实施方案》&#xff0c;国家广电总局对《方案》进行贯彻落实&#xff0c;就“全国一网”整合工作进行动员部署。从“三网融合”到“全国一网”&#xff0c;终于不再只是纸上谈兵&#xff0c;而是已经提上日程。 按照《…

网管型交换机和非网管型交换机的区别

随着互联网的普及&#xff0c;我们对于交换机的需求是越来越多&#xff0c;也是越来越重要。关于交换机这一块&#xff0c;我前面介绍过它的分类&#xff0c;看过的朋友应该都知道&#xff0c;交换机可以分为网管型交换机和非网管型交换机&#xff0c;看其字面意思已经很明确的…

【渝粤教育】国家开放大学2018年秋季 0731-22T书记员工作与实务 参考试题

试卷代号&#xff1a;1013 金融统计分析试题答案及评分标准&#xff08;半开卷&#xff09; &#xff08;供参考&#xff09; 2019年1月 一、单项选择题&#xff08;每小题2分&#xff0c;共40分。每小题有一项答案正确&#xff0c;请将正确答案的序号填在括号内&#xff09; 1…

网络监控系统中如何选择工业交换机?

做网络网络监控系统工程项目的朋友应该都知道&#xff0c;一个中大型网络监控系统不可能单独使用一台或者几台工业交换机就能实现传输效果&#xff0c;这个需要进行工业交换机级联&#xff1a;分别选择不同的工业交换机作为接入层、汇聚层和核心层。你们&#xff0c;我们在做项…

IPTV是什么 IPTV有什么功能

IPTV即交互式网络电视&#xff0c;是一种利用宽带网&#xff0c;集互联网、多媒体、通讯等技术于一体&#xff0c;向用户提供包括数字电视在内的多种交互式服务的崭新技术。它能够很好地适应当今网络飞速发展的趋势&#xff0c;充分有效地利用网络资源。 IPTV既不同于传统的模…

选择交换机需要了解的一些性能参数

工业交换机主要是应用于复杂的工业环境中的实时以太网数据传输&#xff0c;这就要求我们在选购工业交换机时必须对产品的性能有所了解&#xff0c;要求我们所选购的工业交换机产品能够符合当下的工业环境要求。那么&#xff0c;新手在选购工业交换机时主要需要参考那些因素呢&a…

工业以太网交换机的重要技术参数分析

在当下的网络发展进程中&#xff0c;以太网交换机可以说是发挥了很重要的作用&#xff0c;特别是随着ASIC技术的不断发展&#xff0c;以太网交换机在产品性能及价格方面得到了极大的提升&#xff0c;使得以太网交换机技术有了更广阔的发展空间。以太网交换机就是在强大的ASIC芯…

如何在Java中使用Zxing和JFreeSVG创建QR Code SVG?

在本文中&#xff0c;我们将研究如何使用Zxing QR代码生成库和JFreeSVG库在Java中创建QR Code SVG图像。 QR码生成 下面的代码使用Zxing库创建一个表示QR Code的java.awt.image.BufferedImage对象&#xff1a; public static BufferedImage getQRCode(String targetUrl, int w…

以太网应用于控制时存在的问题

现如今&#xff0c;随着网络技术的发展&#xff0c;以太网传输可以说是深受一些大用户的喜爱&#xff0c;但是传统的以太网是一种商用网络&#xff0c;如果要应用到工业控制中还存在一些问题&#xff0c;主要有以下几个方面。接下来我们就跟随飞畅科技的小编一起来看看吧&#…

HDMI高清光端机产品特点及应用场合介绍

HDMI光端机由发送器和接收器组成&#xff0c;能通过单根光纤把计算机主机的音频&#xff0c;视频&#xff0c;USB延长到远端&#xff0c;用户可以在远端实时收听到电脑主机的图像和声音&#xff0c;并使用电脑。同时在电脑主机的近端&#xff0c;也提供了一个DVI&#xff0c;VG…

【渝粤教育】国家开放大学2018年秋季 1289T中国当代文学专题 参考试题

试卷代号&#xff1a;1301 病理生理学 试题 2019年1月 一、单项选择题&#xff08;每题2分&#xff0c;共40分&#xff09; 1.基本病理过程是指( )。 A.每一种疾病的发病机制和规律 B.机体重要系统在不同疾病中出现常见的共同的病理生理变化 C.各系统的不同疾病所共有的致病因素…

java8 base64_Java 8中的Base64 –加入乐趣为时不晚

java8 base64最后&#xff0c;Java 8发布了。 最后&#xff0c;有一种执行Base64编码的标准方法。 长期以来&#xff0c;我们一直依赖于Apache Commons Codec&#xff08;无论如何还是很棒的&#xff09;。 内存敏感的编码人员将拼命使用sun.misc.BASE64Encoder和sun.misc.BASE…

【渝粤教育】国家开放大学2018年秋季 2083T信息技术与教育技术(2) 参考试题

试卷代号&#xff1a;2099 民事诉讼法学 试题 注意事项 2019年1月 一、将你的学号、姓名及分校&#xff08;工作站&#xff09;名称填写在答题纸的规定栏内。考试结束后&#xff0c;把试卷和答题纸放在桌上。试卷和答题纸均不得带出考场。监考人收完 考卷和答题纸后才可离开考…