Skip to content

Latest commit

 

History

History
221 lines (128 loc) · 20 KB

File metadata and controls

221 lines (128 loc) · 20 KB

一、为什么我应该关心测试驱动的开发?

这本书是由开发人员为开发人员编写的。因此,大部分学习将通过代码进行。每章将介绍一个或多个测试驱动开发TDD)实践,我们将通过解决katas来尝试掌握它们。在空手道中,空手道是一种练习,你重复一个动作很多次,每次都没有什么进步。遵循同样的理念,我们将从一章到下一章进行小而重要的改进。您将学习如何更好地设计和编写代码,缩短上市时间TTM),生成始终最新的文档,通过质量测试获得高代码覆盖率,并编写干净有效的代码。

每一次旅行都有一个开始,这次也不例外。我们的目标是一个拥有 TDD 黑带的 Java 开发人员。

为了知道我们要去哪里,我们必须对一些将决定我们航程的问题进行讨论并找到答案。什么是 TDD?这是一种测试技术,还是其他什么?应用 TDD 的好处是什么?

本章的目的是获得 TDD 的概述,了解它是什么,并掌握它为从业者提供的好处。

本章将介绍以下主题:

  • 理解 TDD

  • 什么是 TDD?

  • 测试

  • 嘲笑

  • 可执行文件

  • 无调试

为什么是 TDD?

您可能正在敏捷或瀑布式环境中工作。也许你有经过多年艰苦工作考验的明确程序,或者你刚刚开始自己的创业。无论情况如何,您可能至少面临一种(如果不是更多的话)以下痛苦、问题或不成功分娩的原因:

  • 在创建需求、规范或用户故事的过程中,您的团队的一部分不参与循环
  • 大多数(如果不是全部的话)测试都是手动的,或者根本没有测试
  • 即使您有自动测试,它们也不会检测到真正的问题
  • 自动化测试是在为时已晚,无法为项目提供任何实际价值时编写和执行的
  • 总有比花时间进行测试更紧迫的事情
  • 团队分为测试、开发和功能分析部门,它们常常不同步
  • 由于担心某些东西会被破坏,所以无法重构代码
  • 维修费用太高了
  • 上市时间太长了
  • 客户并不认为交付的是他们所要求的
  • 文档从来都不是最新的
  • 您害怕部署到生产环境,因为结果未知
  • 您通常无法部署到生产环境,因为回归测试运行时间太长
  • 团队花了太多时间试图弄清楚某个方法或类的作用

TDD 并不能神奇地解决所有这些问题。相反,它让我们走上了解决问题的道路。没有什么灵丹妙药,但如果有一种开发实践可以在如此多的层次上产生影响,那就是 TDD。

TDD 加快了上市时间,实现了更容易的重构,有助于创建更好的设计,并促进了更松散的耦合。

除了直接的好处之外,TDD 是许多其他实践的先决条件(连续交付是其中之一)。更好的设计、编写良好的代码、更快的 TTM、最新的文档和可靠的测试覆盖率,是应用 TDD 可以实现的一些结果。

掌握 TDD 并非易事。即使在学习了所有的理论并经历了最佳实践和反模式之后,旅程才刚刚开始。TDD 需要时间和大量实践。这是一次漫长的旅行,不会因为这本书而停止。事实上,它永远不会真正结束。总是有新的方法让你变得更熟练、更快。然而,即使成本很高,收益也更高。花了足够时间使用 TDD 的人声称没有其他方法来开发软件。我们是其中之一,我们相信你也会是。

我们坚信学习一些编码技术的最好方法是编码。你不可能在上班路上的地铁里读完这本书。这不是一本可以在床上读的书。你得把你的手弄脏然后编码。

在本章中,我们将介绍基本知识;从下一步开始,您将通过阅读、编写和运行代码来学习。我们想说的是,当你读完这本书时,你将成为一名经验丰富的 TDD 程序员,但事实并非如此。在本书结束时,您将熟悉 TDD,并在理论和实践方面拥有坚实的基础。剩下的取决于你以及你将在日常工作中应用它所积累的经验。

理解 TDD

此时,你可能在对自己说,“好吧,我知道 TDD 会给我带来一些好处,但 TDD 到底是什么?”TDD 是在实际实施之前编写测试的简单过程。它与传统方法相反,传统方法是在编写代码后执行测试。

红绿重构

TDD 是一个依赖于非常短的开发周期的重复的过程。它基于极限编程XP)的测试优先概念,鼓励具有高度自信的简单设计。驱动此循环的过程称为红绿重构

该程序本身很简单,由几个反复重复的步骤组成:

  1. 写一个测试
  2. 运行所有测试
  3. 编写实现代码
  4. 运行所有测试
  5. 重构
  6. 运行所有测试

因为测试是在实际实现之前编写的,所以应该失败。如果没有,测试就错了。它描述了一些已经存在或编写不正确的内容。在编写测试时处于绿色状态是假阳性的标志。像这样的测试应该被删除或重构。

在编写测试时,我们处于红色状态。 当一个测试的执行完成时,所有的测试都应该通过,然后我们将处于绿色状态。

如果上一次测试失败,则说明实现错误,应予以纠正。要么我们刚刚完成的测试不正确,要么该测试的实现不符合我们设置的规范。如果最后一次测试失败以外的任何测试失败,我们会破坏某些内容,应该恢复更改。

当这种情况发生时,自然的反应是花尽可能多的时间来修复代码,以便所有测试都能通过。然而,这是错误的。如果在几分钟内没有完成修复,最好的办法是恢复更改。毕竟,不久前一切都起了作用。一个破坏了某些东西的实现显然是错误的,那么为什么不回到我们开始的地方,重新考虑一下实现测试的正确方法呢?这样,我们就在一个错误的实现上浪费了几分钟,而不是浪费更多的时间来纠正一开始没有做对的事情。现有的测试覆盖范围(不包括最后一个测试的实现)应该是神圣的。我们通过有意重构来更改现有代码,而不是作为修复最近编写的代码的方法。

不要使最后一个测试的实现成为最终的,而是提供足够的代码来让这个测试通过。

用任何你想要的方式编写代码,但是要快。一旦一切都是绿色的,我们有信心有一个以测试形式存在的安全网。从现在开始,我们可以继续重构代码。这意味着我们在不引入新特性的情况下使代码变得更好、更优化。当重构就位时,所有测试都应该一直通过。

如果在重构过程中,其中一个测试失败,重构会破坏现有的功能,并且像以前一样,应该恢复更改。不仅如此,在这个阶段,我们没有改变任何特性,也没有引入任何新的测试。我们所做的就是在不断运行所有测试以确保没有任何东西被破坏的同时,使代码变得更好。同时,我们正在证明代码的正确性,并降低未来的维护成本。

重构完成后,将重复该过程。这是一个很短周期的无止境循环。

速度是关键

想象一场乒乓球(或乒乓球)比赛。游戏非常快;有时候,当职业球员踢球时,甚至很难跟上球。TDD 非常相似。TDD 老手通常不会花超过一分钟的时间在桌子的两边(测试和实现)。编写一个简短的测试并运行所有测试(ping),编写实现并运行所有测试(pong),编写另一个测试(ping),编写该测试的实现(pong),重构并确认所有测试都通过(score),然后重复 ping,pong,ping,pong,score,再次发球。不要试图制作完美的代码。相反,试着让球滚动,直到你认为是时候得分(重构)。

从测试切换到实现(反之亦然)之间的时间应该以分钟(如果不是秒)来衡量。

这与测试无关

TDD中的T经常被误解。TDD 是我们处理设计的方式。这是迫使我们在编写代码之前考虑实现和代码需要做什么的一种方式。它是一种关注需求和一次只实现一件事情的方法,可以组织您的想法并更好地构建代码。这并不意味着由 TDD 产生的测试是无用的,但事实远非如此。它们非常有用,让我们可以快速发展,而不必担心某些东西会被破坏。当重构发生时,尤其如此。能够重新组织代码,同时确信没有任何功能被破坏,这是对代码质量的巨大提升。

TDD 的主要目标是可测试的代码设计,将测试作为非常有用的副产品。

测试

尽管 TDD 的主要目标是代码设计方法,但测试仍然是 TDD 的一个非常重要的方面,我们应该清楚地了解两大类技术,如下所示:

  • 黑盒测试
  • 白盒试验

黑盒测试

黑盒测试(也称为功能测试)将被测软件视为黑盒,而不知道其内部结构。测试使用软件接口,并尝试确保它们按预期工作。只要接口的功能保持不变,即使内部结构发生变化,测试也应该通过。测试人员知道程序应该做什么,但不知道它是如何做的。黑盒测试是传统组织中最常用的测试类型,这些组织将测试人员作为一个单独的部门,特别是当他们不精通编码并且难以理解时。这项技术提供了一个关于被测软件的外部视角。

黑盒测试的一些优点如下:

  • 它对于大型代码段是有效的
  • 不需要代码访问、理解代码和编写代码的能力
  • 它提供了用户和开发人员视角之间的分离

黑盒测试的一些缺点如下:

  • 它提供的覆盖范围有限,因为只执行了一小部分测试场景
  • 由于测试人员缺乏关于软件内部的知识,这可能导致测试效率低下
  • 这可能导致盲目覆盖,因为测试人员对应用程序的知识有限

如果测试是开发的驱动力,那么测试通常以验收标准的形式进行,验收标准后来被用作开发内容的定义。

自动化黑盒测试依赖于某种形式的自动化,例如行为驱动开发BDD)。

白盒试验

白盒测试(也称为透明盒测试玻璃盒测试透明盒测试结构测试)查看正在测试的软件内部,并将该知识用作测试过程的一部分。例如,如果在某些条件下引发异常,测试可能希望重现这些条件。白盒测试需要系统的内部知识和编程技能。它提供了测试软件的内部透视图。

白盒测试的一些优点如下:

  • 它能有效地发现错误和问题

  • 所需的被测软件内部知识有助于全面测试

  • 它允许查找隐藏的错误

  • 它鼓励程序员反省

  • 它有助于优化代码

  • 由于所需的软件内部知识,获得了最大的覆盖范围

白盒测试的一些缺点如下:

  • 它可能找不到未实现或缺少的功能
  • 它需要被测软件内部的高级知识
  • 它需要代码访问
  • 测试通常与生产代码的实现细节紧密耦合,在重构代码时会导致不必要的测试失败

白盒测试几乎总是自动化的,在大多数情况下,采用单元测试的形式。

在实施前进行白盒测试时,采用 TDD 的形式。

质量检查和质量保证之间的区别

测试方法也可以通过查看他们试图实现的目标来区分。这些目标通常分为质量检查QC)和质量保证QA)。当 QC 关注缺陷识别时,QA 试图防止缺陷。QC 以产品为导向,旨在确保结果符合预期。另一方面,QA 更关注于确保内在质量的过程。它试图确保以正确的方式做正确的事情。

虽然 QC 在过去扮演着更重要的角色,但随着 TDD 的出现,验收测试驱动开发ATDD),以及后来的 BDD,焦点已经转向 QA。

更好的测试

无论是使用黑盒、白盒还是两种类型的测试,它们的编写顺序都非常重要。

需求(规范和用户故事)是在实现它们的代码之前编写的。他们首先来定义代码,而不是相反。测试也是如此。如果它们是在代码完成后以某种方式编写的,则该代码(及其实现的功能)正在定义测试。由现有应用程序定义的测试是有偏差的。他们倾向于确认代码的作用,而不是测试客户的期望是否得到满足,或者代码是否按预期运行。对于手动测试,情况就不是这样了,因为它通常是由一个孤立的 QC 部门完成的(尽管它通常被称为 QA)。他们倾向于独立于开发人员处理测试定义。这本身就导致了更大的问题,这些问题不可避免地会导致沟通不畅和警察综合症,测试人员不是试图帮助团队编写内置质量的应用程序,而是在流程结束时发现错误。我们越早发现问题,解决问题的成本就越低。

以 TDD 方式编写的测试(包括其风格,如 ATDD 和 BDD)试图从一开始就尝试开发具有内置质量的应用程序。 首先是为了避免出现问题。

嘲笑

为了让测试快速运行并提供持续的反馈,代码需要以这样一种方式组织:方法、函数和类可以很容易地替换为模拟和 stub。这种类型的实际代码替换的常用词是测试替身。外部依赖性会严重影响执行速度;例如,我们的代码可能需要与数据库通信。通过模仿外部依赖关系,我们能够大幅提高速度。整个单元测试套件的执行应该以分钟为单位,如果不是秒的话。以一种易于模仿和插桩的方式设计代码迫使我们通过应用关注点分离来更好地构建代码。

比速度更重要的是去除外部因素的好处。设置数据库、Web 服务器、外部 API 和代码可能需要的其他依赖项既耗时又不可靠。在许多情况下,这些依赖项甚至可能不可用。例如,我们可能需要创建一个与数据库通信的代码,并让其他人创建一个模式。如果没有 mock,我们将需要等待该模式被设置。

无论有没有模拟,代码都应该以这样一种方式编写,即我们可以轻松地将一个依赖项替换为另一个依赖项。

可执行文件

TDD(通常是结构良好的测试)的另一个非常有用的方面是文档。在大多数情况下,通过查看测试比查看实现本身更容易了解代码的功能。有些方法的目的是什么?看看与之相关的测试。应用程序 UI 的某些部分所需的功能是什么?看看与之相关的测试。以测试形式编写的文档是 TDD 的支柱之一,值得进一步解释。

(传统)软件文档的主要问题是,它在大多数情况下都不是最新的。一旦代码的某些部分发生更改,文档将停止反映实际情况。本声明适用于几乎所有类型的文档,其中需求和测试用例受影响最大。

编写代码文档的必要性通常是代码本身编写不好的标志。此外,无论我们如何努力,文档都不可避免地会过时。

开发人员不应该依赖于系统文档,因为它几乎从来都不是最新的。此外,没有任何文档能够像代码本身那样提供详细和最新的代码描述。

将代码用作文档并不排除其他类型的文档。关键是避免重复。如果通过阅读代码可以获得系统的详细信息,其他类型的文档可以提供快速指南和高级概述。非代码文档应回答诸如系统的一般用途和系统使用的技术等问题。在许多情况下,一个简单的README就足以提供开发人员所需的快速启动。项目描述、环境设置、安装以及构建和打包说明等部分对新手非常有帮助。从那以后,密码就是圣经。

实现代码提供了所有需要的细节,而测试代码则充当了生产代码背后意图的描述。

测试是可执行文档,TDD 是创建和维护它的最常用方法。

假设正在使用某种形式的持续集成CI),如果测试文档的某些部分不正确,它将失败,并很快得到修复。CI 解决了测试文档不正确的问题,但它不能确保所有功能都被文档化。出于这个原因(在许多其他原因中),测试文档应该以 TDD 方式创建。如果在编写实现代码之前将所有功能定义为测试,并且所有测试的执行都是成功的,那么测试将充当开发人员可以使用的完整且最新的信息源。

我们应该如何对待团队的其他成员?测试人员、客户、经理和其他非编码人员可能无法从生产和测试代码中获得必要的信息。

正如我们前面看到的,两种最常见的测试类型是黑盒测试和白盒测试。这种划分很重要,因为它还将测试人员分为知道如何编写或至少阅读代码的人(白盒测试)和不知道如何编写或至少阅读代码的人(黑盒测试)。在某些情况下,测试人员可以同时进行这两种测试。然而,他们往往不知道如何编码,因此开发人员可以使用的文档对他们来说是不可用的。如果文档需要与代码解耦,那么单元测试就不是很好的匹配。这就是 BDD 产生的原因之一。

BDD 可以为非编码人员提供必要的文档,同时仍然保持 TDD 和自动化的优势。

客户需要能够定义系统的新功能,以及能够获得有关当前系统所有重要方面的信息。文档不应该太技术化(代码不是一个选项),但它仍然必须始终是最新的。BDD 叙述和场景是提供此类文档的最佳方式之一。作为验收标准(在代码之前编写)、频繁执行(最好在每次提交时执行)以及以自然语言编写的能力,使得 BDD 故事不仅始终是最新的,而且对于那些不想检查代码的人来说也是可用的。

文档是软件不可分割的一部分。与代码的任何其他部分一样,它需要经常进行测试,以确保它是准确和最新的。

获得准确和最新信息的唯一经济有效的方法是拥有可以集成到 CI 系统中的可执行文档。

作为一种方法论,TDD 是朝着这个方向前进的一个好方法。在低层次上,单元测试是最合适的。另一方面,BDD 提供了一种在功能级别上工作的好方法,同时保持通过使用自然语言实现的理解。

无调试

我们(这本书的作者)几乎从不调试我们正在开发的应用程序!

这句话听起来可能有些浮夸,但却是真的。我们几乎从不调试,因为很少有理由调试应用程序。当测试是在代码之前编写的,并且代码覆盖率很高时,我们可以对应用程序按预期工作有很高的信心。这并不意味着使用 TDD 编写的应用程序没有 bug。所有应用程序都可以。然而,当这种情况发生时,很容易通过简单地查找测试未涵盖的代码来隔离它们。

测试本身可能不包括某些情况。在这些情况下,操作是编写额外的测试。

在代码覆盖率高的情况下,通过测试找到某些错误的原因比花时间逐行调试直到找到罪魁祸首要快得多。

总结

在本章中,您大致了解了 TDD 实践,并深入了解了什么是 TDD,什么不是 TDD。您了解到,这是一种通过称为红-绿重构的短而可重复的周期来设计代码的方法。失败是一种预期状态,不仅应该接受,而且应该在整个 TDD 过程中强制执行。这一周期如此之短,以至于我们以极快的速度从一个阶段移动到另一个阶段。

虽然代码设计是主要目标,但在整个 TDD 过程中创建的测试是一项宝贵的资产,应该加以利用,并严重影响我们对传统测试实践的看法。我们经历了最常见的实践,如白盒测试和黑盒测试,试图将它们放到 TDD 的角度,并展示了它们可以相互带来的好处。

您发现模拟是非常重要的工具,在编写测试时经常是必须的。最后,我们讨论了如何将测试用作可执行文档,以及 TDD 如何减少调试的必要性。

现在我们已经掌握了理论知识,是时候建立开发环境并对不同的测试框架和工具进行概述和比较了。