开发罪过_七大罪过与如何避免

开发罪过

在整个本文中,我将在代码片段中使用Java,同时还将使用JUnit和Mockito 。

本文旨在提供以下测试代码示例:

  • 难以阅读
  • 难以维护

在这些示例之后,本文将尝试提供替代方法,这些替代方法可用于增强测试的可读性,从而有助于使其在将来更易于维护。

创建良好的示例具有挑战性,因此,作为读者,我鼓励您将示例仅用作了解本文基本信息的工具,以力求实现可读的测试代码。

1.通用测试名称

您可能已经看到了如下所示的测试

@Test
void testTranslator() {String word = new Translator().wordFrom(1);assertThat(word, is("one"));
}

现在这是非常通用的,不会通知代码的读者该测试实际在测试什么。 Translator可能有多种方法,我们如何知道测试中正在使用哪种方法? 通过查看测试名称并不清楚,这意味着我们必须查看测试本身才能看到。

我们可以做得更好,因此可以看到以下内容:

@Test
void translate_from_number_to_word() {String word = new Translator().wordFrom(1);assertThat(word, is("one"));
}

从上面的内容可以看出,它在解释此测试的实际作用方面做得更好。 此外,如果您将测试文件命名为TranslatorShould那么在将测试文件和单个测试名称组合在一起时,您应该在头脑中形成一个合理的句子: Translator should translate from number to word

2.测试设置中的变异

在测试中,您很有可能希望将测试中使用的对象构造为处于特定状态。 有不同的方法,下面显示了一种这样的方法。 在此代码段中,我们基于该对象中包含的信息来确定某个字符是否实际上是“ Luke Skywalker”(想象这就是isLuke()方法的作用):

@Test
void inform_when_character_is_luke_skywalker() {StarWarsTrivia trivia = new StarWarsTrivia();Character luke = new Character();luke.setName("Luke Skywalker");Character vader = new Character();vader.setName("Darth Vader");luke.setFather(vader);luke.setProfession(PROFESSION.JEDI);boolean isLuke = trivia.isLuke(luke);assertTrue(isLuke);
}

上面的代码构造了一个Character对象来表示“ Luke Skywalker”,此后发生的事涉及相当比例的突变。 它继续在随后的行中设置名称,父母身份和职业。 当然,这忽略了与我们的朋友“达斯·维达”发生的类似事情。

这种突变水平分散了测试中正在发生的事情。 如果我们再回顾一下我先前的句子:

在测试中很有可能您希望将测试中使用的对象构造为处于特定状态

但是,上述测试实际上发生了两个阶段:

  • 构造对象
  • 使其处于某种状态

这是不必要的,我们可以避免。 可能有人建议,为了避免发生突变,我们可以简单地将所有内容都移植并转储到构造函数中,以确保我们以给定的状态构造对象,避免发生突变:

@Test
void inform_when_character_is_luke_skywalker() {StarWarsTrivia trivia = new StarWarsTrivia();Character vader = new Character("Darth Vader");Character luke = new Character("Luke Skywalker", vader, PROFESSION.JEDI);boolean isLuke = trivia.isLuke(luke);assertTrue(isLuke);
}

从上面可以看到,我们减少了代码行的数量以及对象的变异。 但是,在此过程中,我们已经失去了Character (现在为Character参数)在测试中表示的含义。 为了使isLuke()方法返回true,我们传入的Character对象必须具有以下内容:

  • “卢克·天行者”的名字
  • 有一个父亲叫“达斯·维达”
  • 成为绝地武士

但是,从这种情况的测试中尚不清楚,我们必须检查Character的内部以了解这些参数的用途(否则您的IDE会告诉您)。

我们可以做得更好,可以利用Builder模式在所需状态下构造一个Character对象,同时还可以保持测试的可读性:

@Test
void inform_when_character_is_luke_skywalker() {StarWarsTrivia trivia = new StarWarsTrivia();Character luke = CharacterBuilder().aCharacter().withNameOf("Luke Skywalker").sonOf(new Character("Darth Vader")).employedAsA(PROFESSION.JEDI).build();boolean isLuke = trivia.isLuke(luke);assertTrue(isLuke);
}

通过上面的内容,可能还会有几行内容,但是它试图解释测试中的重要内容。

3.断言疯狂

在测试期间,您将断言/验证系统中是否发生了某些事情(通常位于每次测试结束时)。 这是测试中非常重要的一步,可能很想添加许多断言,例如断言返回对象的值。

@Test
void successfully_upgrades_user() {UserService service = new UserService();User someBasicUser = UserBuilder.aUser().withName("Basic Bob").withAge(23).withTypeOf(UserType.BASIC).build();User upgradedUser = service.upgrade(someBasicUser);assertThat(upgradedUser.name(), is("Basic Bob"));assertThat(upgradedUser.type(), is(UserType.SUPER_USER));assertThat(upgradedUser.age(), is(23));
}

(在上面的示例中,我向构建器提供了其他信息,例如名称和年龄,但是,如果对测试不重要,则通常不会包含此信息,请在构建器中使用明智的默认值)

如我们所见,存在三个断言,在更极端的示例中,我们谈论的是数十行断言。 我们不一定需要执行三个断言,有时我们可以合而为一:

@Test
void successfully_upgrades_user() {UserService service = new UserService();User someBasicUser = UserBuilder.aUser().withName("Basic Bob").withAge(23).withTypeOf(UserType.BASIC).build();User expectedUserAfterUpgrading = UserBuilder.aUser().withName("Basic Bob").withAge(23).withTypeOf(UserType.SUPER_USER).build();User upgradedUser = service.upgrade(someBasicUser);assertThat(upgradedUser, is(expectedUserAfterUpgrading));
}

现在,我们将升级后的用户与我们期望对象在升级后的外观进行比较。 为此,您将需要比较的对象( User )具有覆盖的equalshashCode

4.神奇的价值观

您是否曾经看过数字或字符串并想知道它代表什么? 我已经拥有了那些不得不解析代码行的宝贵时间,这些时间很快就会开始累加起来。 我们在下面有一个这样的代码示例。

@Test
void denies_entry_for_someone_who_is_not_old_enough() {Person youngPerson = PersonBuilder.aPerson().withAgeOf(17).build();NightclubService service = new NightclubService(21);String decision = service.entryDecisionFor(youngPerson);assertThat(decision, is("No entry. They are not old enough."));
}

阅读以上内容,您可能会遇到一些问题,例如:

  • 17是什么意思?
  • 21在构造函数中是什么意思?

如果我们可以向代码读者表示它们的含义,那不是很好,那么他们不必考虑太多吗? 幸运的是,我们可以:

private static final int SEVENTEEN_YEARS = 17;
private static final int MINIMUM_AGE_FOR_ENTRY = 21;
private static final String NO_ENTRY_MESSAGE = "No entry. They are not old enough.";@Test
void denies_entry_for_someone_who_is_not_old_enough() {Person youngPerson = PersonBuilder.aPerson().withAgeOf(SEVENTEEN_YEARS).build();NightclubService service = new NightclubService(MINIMUM_AGE_FOR_ENTRY);String decision = service.entryDecisionFor(youngPerson);assertThat(decision, is(NO_ENTRY_MESSAGE));
}

现在,当我们看以上内容时,我们知道:

  • SEVENTEEN_YEARS是用来表示17年的值,毫无疑问,我们已经在读者的脑海中留下了疑问。 不是秒或分钟,而是年。
  • MINIMUM_AGE_FOR_ENTRY是必须允许某人进入夜总会的值。 读者甚至不必关心此值是什么,只需了解测试上下文中的含义即可。
  • NO_ENTRY_MESSAGE是返回的值,表示不允许某人进入夜总会。 从本质上讲,字符串通常具有更好的描述性,但是请始终检查您的代码以找出可以改进的地方。

这里的关键是减少代码阅读器尝试解析代码行所花费的时间。

5.难以读取的测试名称

@Test
void testingNumberOneAndNumberTwoCanBeAddedTogetherToProduceNumberThree() {...
}

您花了多长时间阅读以上内容? 它易于阅读吗?您能快速了解一下此处正在测试的内容吗?还是需要解析许多字符?

幸运的是,我们可以尝试以更好的方式命名测试,方法是将测试减少到实际测试的水平,并删除试图添加的华夫饼:

@Test
void twoNumbersCanBeAdded() {...
}

它的阅读效果更好吗? 我们减少了这里的单词数量,更易于解析。 如果我们可以更进一步,问我们是否可以放弃使用骆驼箱怎么办:

@Test
void two_numbers_can_be_added() {...
}

这是一个优先事项,应该由对给定代码库做出贡献的人员同意。 使用蛇形小写字母(如上所述)可以帮助提高测试名称的可读性,因为您更可能打算模仿书面句子。 因此,蛇形格的使用紧随普通书面句子中存在的物理空间。 但是,Java不允许在方法名称中使用空格,这是我们所拥有的最好的方法,缺少使用Spock之类的东西。

6.依赖项注入的设置器

通常,对于测试,您希望能够为给定对象(也称为“协作对象”或简称为“协作者”)注入依赖关系。 为了达到这个目的,您可能已经看到了类似以下内容的内容:

@Test
void save_a_product() {ProductService service = new ProductService();TestableProductRepository repository = mock(TestableProductRepository.class);service.setRepository(repository);Product newProduct = new Product("some product");service.addProduct(newProduct);verify(repository).save(newProduct);
}

上面使用了setter方法,即setRepository() ,以便注入TestableProductRepository的模拟,因此我们可以验证服务和存储库之间是否发生了正确的协作。

与围绕突变的点类似,这里我们对ProductService进行突变,而不是将其构造为所需状态。 可以通过将协作者注入构造函数中来避免这种情况:

@Test
void save_a_product() {TestableProductRepository repository = mock(TestableProductRepository.class);ProductService service = new ProductService(repository);Product newProduct = new Product("some product");service.addProduct(newProduct);verify(repository).save(newProduct);
}

因此,现在我们将协作者注入了构造函数中,现在我们在构造时就知道对象将处于什么状态。但是,您可能会问“在此过程中我们是否没有丢失某些上下文?”。

我们已经从

service.setRepository(repository);

ProductService service = new ProductService(repository);

前者更具描述性。 因此,如果您不喜欢这种上下文丢失的情况,则可以选择类似构建器的内容,而创建以下内容:

@Test
void save_a_product() {TestableProductRepository repository = mock(TestableProductRepository.class);ProductService service = ProductServiceBuilder.aProductService().withRepository(repository).build();Product newProduct = new Product("some product");service.addProduct(newProduct);verify(repository).save(newProduct);
}

该解决方案使我们能够避免在使用withRepository()方法记录协作者注入的情况下改变ProductService

7.非描述性验证

如前所述,您的测试通常会包含验证语句。 不用自己动手,您通常会利用库来执行此操作。 但是,您必须注意不要掩盖验证的意图。 要了解我在说什么,请看以下示例。

@Test
void no_error_is_shown_when_user_is_valid() {UIComponent component = mock(UIComponent.class);User user = mock(User.class);when(user.isValid()).thenReturn(true);LoginController controller = new LoginController();controller.attemptLogin(component, user);verifyZeroInteractions(component);
}

现在,如果您看上面的内容,您是否立即知道该断言表明没有错误显示给用户? 可能是因为它是测试的名称,但是您可能不将该代码行与测试名称相关联 。 这是因为它是Mockito的代码,并且通用以适应许多不同的用例。 它按照它说的做,检查与UIComponent的模拟是否没有交互。

但是,这意味着您的测试有所不同。 我们如何设法使其更加清晰。

@Test
void no_error_is_shown_when_user_is_valid() {UIComponent component = mock(UIComponent.class);User user = mock(User.class);when(user.isValid()).thenReturn(true);LoginController controller = new LoginController();controller.attemptLogin(component, user);verify(component, times(0)).addErrorMessage("Invalid user");
}

这样会更好一些,因为此代码的读者有很大的潜力可以快速了解此行的工作。 但是,在某些情况下,可能仍然很难阅读。 在这种情况下,请按照以下说明提取一种方法,以更好地解释您的验证。

@Test
void no_error_is_shown_when_user_is_valid() {UIComponent component = mock(UIComponent.class);User user = mock(User.class);when(user.isValid()).thenReturn(true);LoginController controller = new LoginController();controller.attemptLogin(component, user);verifyNoErrorMessageIsAddedTo(component);
}private void verifyNoErrorMessageIsAddedTo(UIComponent component) {verify(component, times(0)).addErrorMessage("Invalid user");
}

上面的代码并不完美,但是在当前测试的范围内,它肯定可以提供我们正在验证的内容的高级概述。

结束语

我希望您喜欢这篇文章,并且下次您完成编写测试时将花费一两个重构步骤。 在下一次之前,我给你以下报价:

“必须编写程序供人们阅读,并且只能偶然地使机器执行。” ― Harold Abelson,计算机程序的结构和解释

翻译自: https://www.javacodegeeks.com/2019/08/seven-testing-sins-and-how-to-avoid-them.html

开发罪过

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

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

相关文章

调用门的定义+调用

【0】写在前面 0.1)本代码,添加了门描述符的相关代码,旨在说明 怎样 对门转移的目标段 进行定义,调用;0.2)本文 只对 与 门相关的 代码进行简要注释,言简意赅;0.3)文末总…

小学生图片_中秋节手抄报,小学生中秋节手抄报图片大全

月饼,或叫中秋饼,是东亚各地的中秋节食品,越南称为饼中秋(Bnhtrungthu)。中秋节吃月饼的习俗于唐朝开始,北宋之时,月饼被称为“宫饼”,在宫廷内流行,也流传到民间,俗称“小饼”和“月…

Response的学习笔记(属Servlet学习课程)

文章目录Response设置响应消息数据1.设置响应行2.设置响应头3.设置响应体案例1.重定向2.输出字符数据到 Response 对象乱码的问题解决办法一解决办法二3.输出字节数据到 Response 对象4.验证码案例继承与实现体系Response 该对象的功能就是用来设置响应消息(响应报…

如何写一个高效进程/线程池_关于高效企业测试的思考(1/6)

如何写一个高效进程/线程池企业中的测试仍然没有得到应有的广泛应用。 编写尤其是维护测试需要花费时间和精力,但是缩短软件测试并不是解决方案。 为了提高测试效率,应该追求哪些范围,方法和测试技术? 基于许多实际项目&#xff…

智能音箱音效哪个好_华为支浩:音质好是底线,AI基本功扎实让智能音箱不再是“玩具”...

2020年,智能音箱市场的仗已经打了六年。立足国内,抑或放眼国际,似乎都是胜负已分,江山已定。可就在此时,一位“搅局者”闯入了大家视野。10月30日,华为年度旗舰新品发布会在上海举行。会上,华为…

JS(JavaScript)给元素绑定事件/给元素注册事件处理程序/给元素注册事件监听器

文章目录不使用 jQuery&#xff0c;给元素注册事件监听器通过jQuery给元素注册事件监听器通过标签的属性来注册事件监听器不使用 jQuery&#xff0c;给元素注册事件监听器 <script type"text/javascript">window.onload function () {alert("test"…

ssh无密码登陆权威指南

##【0】写在前面 ###由于ssh 实现的是免密码登陆&#xff0c;大致步骤是&#xff1a; 0.1&#xff09; client通过ssh登陆到server&#xff1b;0.2&#xff09; server检查家目录下的.ssh文件&#xff0c; 并发送公钥文件 authorized_keys 到client &#xff1b;0.3&#xff0…

java 十六进制浮点_Java十六进制浮点文字

java 十六进制浮点我如何遇到十六进制浮点数 我正在Java :: Geci中开发一种新功能&#xff0c;以减少代码重新格式化的可能性。 如果重新格式化&#xff0c;当前版本的代码将覆盖原本相同的代码。 这很烦人&#xff0c;因为按下重新格式化键的快捷键相当容易&#xff0c;而且许…

python时间倒计时显示屏厂家_python 实现倒计时功能(gui界面)

运行效果&#xff1a;完整源码&#xff1a;##import libraryfrom tkinter import *import timefrom playsound import playsound## display windowroot tk()root.geometry(400x300)root.resizable(0,0)root.config(bg blanched almond)root.title(techvidvan - countdown cloc…

IntelliJ IDEA中Maven操作窗口的命令详解/Maven命令详解/Maven生命周期命令详解

文章目录生命周期命令简介命令详解testpackage命令演示verifyinstall将当前项目放到 Maven 的本地仓库中&#xff0c;供其他项目使用将自己打包或者下载的 jar/war 文件复制到本地仓库中&#xff0c;供其他模块使用sitedeploy生命周期命令简介 在 Maven 中&#xff0c;项目构建…

基于Apache POI 向xlsx写入数据

【0】写在前面 0.1&#xff09; these codes are from 基于Apache POI 的向xlsx写入数据0.2&#xff09; this idea is from http://cwind.iteye.com/blog/2187670 , adding some comments for easy understanding proves to be my work. package com.cwind.poi; import jav…

javafx 打开新窗口_新的JMetro JavaFX 11兼容版本

javafx 打开新窗口你好&#xff0c;我们又见面了&#xff01; 这次&#xff0c;新版本与JavaFX 11兼容。 继续阅读以获取详细信息。 JMetro 8.5.7和11.5.7版本 JMetro代码已分为2个分支。 master分支具有Java 8兼容的JMetro版本&#xff0c;“ 11”分支具有Java 11兼容的版本…

后勤管理系统_充满“智慧”的后勤管理系统是什么样的?

▲2020年4月7日&#xff0c;亿力信息公司部署的后勤管理系统二期上线试运行&#xff0c;技术人员在现场指导用户进行系统的基本操作后勤管理系统二期全面提升管理效能近日&#xff0c;亿力信息公司承接部署的后勤管理系统二期已上线试运行&#xff0c;通过贯穿资产全寿命周期的…

Final Cut Pro 在视频的多个地方同时打马赛克

文章目录使用风格化效果中的像素化或者模糊效果使用风格化效果中的删减效果让马赛克显示一段时间后消失使用风格化效果中的像素化或者模糊效果 像素化和模糊效果是将整个视频画面都像素化和模糊化&#xff0c;所以如果你要将画面中的某个部分打马赛克&#xff0c;其实这样的效…

亚麻纤维截面形态_天然丝纤维蚕丝

点击蓝字 关注我们我国是蚕丝的发源地。近年来&#xff0c;对出土文物的考古研究指出&#xff0c;蚕丝在我国已有六千多年的历史。柞蚕丝也起源于我国&#xff0c;根据历史记载&#xff0c;已有三千多年的历史。远在汉、唐时代&#xff0c;我国的丝绸就畅销于中亚和欧洲各国&am…

jep122_JEP 358:有用的NullPointerExceptions

jep122在文章“ 更好的默认NullPointerException消息是否会传入Java&#xff1f; ”&#xff0c;我总结了当时与JEP 草案有关的背景细节&#xff0c;有关使某些类型的NullPointerException &#xff08;NPE&#xff09;消息更有用。 上周很高兴看到该JEP现在是候选 JEP &#x…

目录、文件夹、文件三者的区别

目录也是文件&#xff0c;是一种特殊文件&#xff0c;叫目录文件&#xff0c;简称目录。 目录是文件系统对象&#xff0c;属于文件系统的概念 术语目录指的是文档文件和文件夹的结构化列表存储在计算机上的方式。它与包含姓名、号码和地址列表的电话簿相当&#xff0c;并且不包…

基于Apache POI 从xlsx读出数据

【0】写在前面 0.1&#xff09; these codes are from 基于Apache POI 的从xlsx读出数据0.2&#xff09; this idea is from http://cwind.iteye.com/blog/2187670 , adding some comments for easy understanding proves to be my work. package com.cwind.poi; import jav…

toarray方法_机器学习中类别变量的编码方法总结

作者&#xff1a;louwill&#xff1b;转载自&#xff1a;机器学习实验室在做结构化数据训练时&#xff0c;类别特征是一个非常常见的变量类型。机器学习中有多种类别变量编码方式&#xff0c;各种编码方法都有各自的适用场景和特点。本文就对机器学习中常见的类别编码方式做一个…

java实现可选形参_Java:可选的可选实现

java实现可选形参类java.util.Optional被实现为单个不可变的具体类&#xff0c;该类在内部处理两种情况。 一个有元素&#xff0c;一个没有元素。 让Optional作为一个接口并让两个不同的实现代替实现该接口不是更好的选择吗&#xff1f; 毕竟&#xff0c;这就是我们通常被教导要…