windows10罪过

在整个本文中,我将在代码段中使用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

windows10罪过

windows10罪过_七大罪过与如何避免相关推荐

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

    开发罪过 在整个本文中,我将在代码片段中使用Java,同时还将使用JUnit和Mockito . 本文旨在提供以下测试代码示例: 难以阅读 难以维护 在这些示例之后,本文将尝试提供替代方法,这些替代方 ...

  2. 禁用windows10更新_如何在Windows 10中禁用投影

    禁用windows10更新 The drop shadows on applications in the Windows 10 preview are really big and suspicio ...

  3. windows10升级助手_利用系统自带应用在Windows 10上实现电脑免费拨打电话

    编辑 | 排版 | 制图 | 测试 | ©伯衡君© 未经允许,谢绝转载来源:官方网站 开篇寄语 伯衡君在两个月前免费从windows7更新到了windows10,如何可以免费更新到windows10? ...

  4. 实数集r用区间表示为_七大实数理论与互推

    七大实数理论简介 (一)确界原理 定义1.1: 是一个非空数集, 是一个常数,若 ,有 ,则称 是数集 的一个上界.同理,若 ,有 ,则称 是数集 的一个下界. 定义1.2:若 是数集 的一个上界,并 ...

  5. 实数系的基本定理_七大实数理论与互推

    七大实数理论简介 (一)确界原理 定义1.1: 是一个非空数集, 是一个常数,若 ,有 ,则称 是数集 的一个上界.同理,若 ,有 ,则称 是数集 的一个下界. 定义1.2:若 是数集 的一个上界,并 ...

  6. windows10墙纸_如何在Windows 7中的多台显示器上使用不同的墙纸

    windows10墙纸 So you've just unpacked that spiffy new monitor, and it sits fresh and new on your desk ...

  7. 七大行星排列图片_七大行星大小排列顺序,其实是八大(水星最小/木星最大)【图文】...

    豹子奇闻网摘要:小编根据网络热点将"七大行星大小排列顺序,其实是八大(水星最小/木星最大)[图文]"的详细内容都整理出来放在以下内容中! 关于七大行星大小排列顺序,如果按照离太阳距 ...

  8. 禁用windows10更新_如何在Windows 10上使用(或禁用)Windows Ink工作区

    禁用windows10更新 Windows 10's Anniversary Update improves on Windows 10's stylus support with a new &qu ...

  9. 10系统怎么进入服务器,开机无法进入windows10系统_网站服务器运行维护

    win7电脑提示当前安全设置会使计算机有风险_网站服务器运行维护 win7电脑提示当前安全设置会使计算机有风险的解决方法是:1.首先,打开计算机配置:2.然后,依次打开[管理模板].[windows组 ...

最新文章

  1. php column not found,java.sql.SQLException: Column 'cloumn name' not found.
  2. android中Listview的优化技巧
  3. DeepLearning——CNN
  4. java设计模式---模板方法模式
  5. java linux urlencode_iOS urlEncode编码解码(非过时方法,已解决)
  6. nyoj1311勤奋的涟漪
  7. fun是什么意思 python中def_【python】 numpy中的矩阵转置(ndarray.T)为什么不加括号却可以实现方法的功能...
  8. 更改微信小程序的基础版本库;更改uni-app小程序基础库;更改用户的微信小程序基础库最低版本;设置用户的微信小程序版本库;
  9. java痴和堆_JAVA虚拟机理解 - 爱笑的痴迷者的个人空间 - OSCHINA - 中文开源技术交流社区...
  10. 数据库设计(关系型)
  11. 如何保证数据库结构的合理性(三、建立可靠的关系)
  12. 职场“35岁危机”:这是我看过的最棒建议
  13. 13.docker exec
  14. httpclient 连接池工具类_C# 中 HttpClient 的简单使用
  15. 决策树 结构_如何快速简单的理解决策树的概念?
  16. 电子数字计算机和电子模拟计算机区别,电子数字计算机和电子模拟计算机的区别在哪里?...
  17. python中平方和_python的平方和怎么理解?
  18. 斑马Zebra 110Xi4 打印机驱动
  19. 数学建模学习笔记---Mooc1
  20. 搭NAS or 租OSS

热门文章

  1. 【docker】Mac下oracle10g下载安装
  2. excel计算机一级打不开,电脑上的所有excel表格都打不开怎么处理?
  3. 银行网申计算机技能怎么填,邮政储蓄银行网申填写技巧分享二
  4. 使用3DMax制作一个象棋棋子
  5. spring cloudAlibaba gateway网关报错,显示无法找到注册中心注册自己。
  6. 把梳子卖给和尚的故事(网络营销策略,精彩案例分析)
  7. jwt的token要存mysql吗_认证的token不存到数据库
  8. 微软必应成功预测法国队夺冠
  9. 工具-python包-虚拟环境管理(99.4.1)
  10. Fractal Streets(经典分形递归+坐标旋转)