JavaRush /Java 博客 /Random-ZH /什么是TDD和单元测试[翻译]
Dr-John Zoidberg
第 41 级
Марс

什么是TDD和单元测试[翻译]

已在 Random-ZH 群组中发布
本文改编自《完整软件职业指南》一书的一章。它的作者 John Sonmez 撰写了该书,并在他的网站上发布了一些章节。
什么是 TDD 和单元测试 [翻译] - 1

适合初学者的简短词汇表

单元测试或单元测试是编程中的一个过程,允许您检查程序源代码的各个模块的正确性。这个想法是为每个重要的函数或方法编写测试。 回归测试是所有类型软件测试的总称,旨在检测源代码已测试区域中的错误。此类错误(当对程序进行更改后,应该继续工作的某些内容停止工作时)称为回归错误。 红色结果,失败- 测试失败。预期结果与实际结果之间的差异。 绿色结果,通过- 阳性测试结果。实际结果与得到的结果没有什么不同。***
什么是 TDD 和单元测试 [翻译] - 2
我对测试驱动开发(TDD)和单元测试的关系非常复杂,从爱到恨,然后又回来。我是一个狂热的粉丝,同时对这个和其他“最佳实践”的使用持怀疑态度。我之所以持这种态度,是因为软件开发过程中出现了一个严重的问题:开发人员,有时甚至是管理者,使用某些工具和方法只是因为它们属于“最佳实践”。使用它们的真正原因仍不清楚。有一天,我开始从事某个项目,在此过程中我被告知我们将修改大量单元测试所涵盖的代码。不是开玩笑,大约有 3000 个。这通常是一个好兆头,表明开发人员正在使用先进的方法。采用这种方法的代码通常是结构化的,并且基于深思熟虑的架构。总之,测试的存在让我很高兴,因为这意味着让我作为程序员导师的工作变得更轻松。由于我们已经进行了单元测试,我所要做的就是联系开发团队来支持他们并开始编写我们自己的代码。我打开IDE(集成开发环境)并加载项目。
什么是 TDD 和单元测试 [翻译] - 3
这是一个大工程!我找到了一个标有“单元测试”的文件夹。“太棒了,”我想。- 让我们启动它,看看会发生什么。只花了几分钟,令我惊讶的是,所有测试都通过了,一切都是绿色的(“绿色”是测试的积极结果。表示代码按预期工作。红色表示“失败”或失败,然后在某些情况下,代码无法正常工作 - 译者注)。他们都通过了测试。那一刻,我内心的怀疑醒了。为什么三千个单元测试,他们一次全部进行,并给出了积极的结果?在我长期的实践中,我不记得有一次我开始从事一个项目时,代码中没有一个负面的单元测试。该怎么办?手动检查!ChY 选择了一项随机测试,虽然不是最具启发性的测试,但很快就很清楚他在检查什么。但在研究过程中,我注意到一些荒谬的事情:测试不包含与预期结果(断言)的任何比较!也就是说,实际上根本没有检查任何内容!测试中有某些步骤,它们被执行了,但是在测试结束时,他应该比较实际结果和预期结果,但没有检查。“测试”没有测试任何东西。我打开了另一个测试。更好的是:带有结果的比较运算符已被注释掉。太棒了!这是进行测试通过的好方法,只需注释掉导致测试失败的代码即可。我检查了另一个测试,然后又检查了一个……他们都没有检查任何东西。三千次测试,全部毫无用处。编写单元测试与理解单元测试和测试驱动开发(TDD)之间存在巨大差异。

什么是单元测试?

什么是 TDD 和单元测试 [翻译] - 4
单元测试的基本思想是编写测试代码的最小“单元”的测试。单元测试通常使用与应用程序源代码相同的编程语言编写。它们是直接创建来测试此代码的。也就是说,单元测试是检查其他代码正确性的代码。我在上下文中相当随意地使用“测试”这个词,因为单元测试在某种意义上不是测试。他们没有经历任何事情。我的意思是,当您运行单元测试时,您通常不会发现某些代码不起作用。您在编写测试时会发现这一点,因为您将更改代码直到测试变绿。是的,代码稍后可能会更改,然后您的测试可能会失败。所以从这个意义上来说,单元测试就是回归测试。单元测试与普通测试不同,在普通测试中,您需要遵循几个步骤来查看软件是否正常工作。在编写单元测试的过程中,您会发现代码是否做了它应该做的事情,并且您将更改代码直到测试通过。
什么是 TDD 和单元测试 [翻译] - 5
为什么不编写一个单元测试并检查它是否通过?如果你这样想的话,那么单元测试就会变成对某些代码模块在非常低的级别上的某种绝对要求。您可以将单元测试视为绝对规范。单元测试确定在这些条件下,使用这组特定的输入,您应该从该代码单元获得输出。真正的单元测试识别最小的连贯代码单元,在大多数编程语言(至少是面向对象的语言)中,它是一个类。

有时所谓的单元测试是什么?

什么是 TDD 和单元测试 [翻译] - 6
单元测试经常与集成测试混淆。一些“单元测试”测试多个类或测试大的代码单元。许多开发人员声称他们编写了单元测试,但实际上他们编写的是低级白盒测试。不要和这些人争论。只需知道他们实际上正在编写集成测试,而真正的单元测试正在测试与其他部分隔离的最小代码单元。另一件通常称为单元测试的事情是不检查预期值的单元测试。换句话说,单元测试实际上并不测试任何东西。任何测试,无论是否统一,都必须包括某种验证 - 我们称之为根据预期结果检查实际结果。正是这种协调决定了测试是通过还是失败。总是通过的测试是没有用的。总是失败的测试是没有用的。

单元测试的价值

为什么我是单元测试爱好者?为什么称之为广义测试是有害的,广义测试涉及的不是测试与其他代码隔离的最小块,而是测试更大的代码段,即“单元测试”?如果我的某些测试未将收到的结果与预期结果进行比较,会出现什么问题?至少他们执行代码。我会尝试解释一下。
什么是 TDD 和单元测试 [翻译] - 7
执行单元测试有两个主要原因。 首先是改进代码设计。 还记得我说过单元测试不是真正的测试吗?当您编写正确的单元测试时,您会强迫自己隔离最小的代码单元。这些尝试将引导您发现代码本身结构中的问题。您可能会发现隔离测试类并且不包含其依赖项非常困难,这可能会让您意识到您的代码耦合得太紧密。您可能会发现您尝试测试的核心功能跨越多个模块,导致您认为您的代码不够连贯。当您坐下来编写单元测试时,您可能会突然发现(相信我,这种情况确实发生了!)您不知道代码应该做什么。因此,您将无法为其编写单元测试。当然,您可能会在代码的实现中发现真正的错误,因为单元测试迫使您跳出框框思考并测试您可能没有考虑过的不同输入集。
什么是 TDD 和单元测试 [翻译] - 8
如果您在创建单元测试时严格遵守“独立于其他代码单元测试最小的代码单元”的规则,那么您一定会发现该代码以及这些模块的设计存在各种各样的问题。在软件开发生命周期中,单元测试更多的是一种评估活动而不是测试活动。 单元测试的第二个主要目标是创建一组自动化的回归测试,可以充当软件行为的低级规范。这是什么意思?当你揉面团时,你不会把它弄碎。从这个角度来看,单元测试就是测试,更具体地说是回归测试。然而,单元测试的目的并不是简单地构建回归测试。在实践中,单元测试很少捕获回归,因为对正在测试的代码单元的更改几乎总是包含对单元测试本身的更改。当代码作为“黑盒”进行测试时,回归测试在更高级别上更加有效,因为在这个级别上,代码的内部结构可以更改,而外部行为预计保持不变。单元测试依次检查内部结构,以便当该结构发生变化时,单元测试不会失败。它们变得无法使用,现在需要更改、丢弃或重写。您现在比许多经验丰富的软件开发人员更了解单元测试的真正目的。

什么是测试驱动开发(TDD)?

什么是 TDD 和单元测试 [翻译] - 9
在软件开发过程中,好的规范是非常有价值的。TDD 方法是,在编写任何代码之前,首先编写一个测试作为规范,即定义代码应该做什么。这是一个极其强大的软件开发概念,但它经常被误用。通常,测试驱动开发意味着使用单元测试来指导应用程序代码的创建。但事实上,这种方法可以应用于任何级别。但是,在本文中,我们将假设我们正在对应用程序使用单元测试。TDD 方法彻底颠覆了一切,您不是先编写代码,然后编写单元测试来测试该代码,而是先编写单元测试,然后编写代码以使该测试变得绿色。通过这种方式,单元测试“驱动”代码开发。这个过程一遍又一遍地重复。您编写另一个测试来定义代码应该执行的更多功能。然后,您编写并修改代码以确保测试成功完成。一旦获得绿色结果,就开始重构代码,即重构或清理它以使其更加简洁。这一过程链通常被称为“红-绿-重构”,因为首先单元测试失败(红色),然后编写代码以适应测试,确保其成功(绿色),最后优化代码(重构)。

TDD 的目标是什么?

什么是 TDD 和单元测试 [翻译] - 10
测试驱动开发(TDD)与单元测试一样,也可能被错误地使用。人们很容易将您所做的事情称为“TDD”,甚至在不理解为什么这样做的情况下遵循实践。TDD 的最大价值在于执行测试以产生质量规范。 TDD本质上是编写精确规范的实践,这些规范可以在编写代码之前自动检查。测试是最好的规范,因为它们不会说谎。经过两周的折磨后,他们不会告诉你“这根本不是我的意思”。如果编写正确,测试要么通过,要么失败。测试清楚地表明在某些情况下应该发生什么。因此,TDD 的目标是让我们在开始实施之前全面了解需要实施的内容。如果您刚开始使用 TDD 并且无法弄清楚测试应该测试什么,那么您需要提出更多问题。TDD 的另一个重要作用是保存和优化代码。代码维护成本高昂。我经常开玩笑说最好的程序员是编写解决问题的最短代码的人。或者甚至证明这个问题不需要解决,从而完全删除代码,因为正是这位程序员找到了减少错误数量并降低维护应用程序成本的正确方法。通过使用 TDD,您可以绝对确定您没有编写任何不必要的代码,因为您编写的代码只是为了通过测试。有一个称为 YAGNI 的软件开发原则(你不会需要它)。TDD 阻止了 YAGNI。

典型的测试驱动开发 (TDD) 工作流程

什么是 TDD 和单元测试 [翻译] - 11
从纯粹的学术角度理解 TDD 的含义是困难的。让我们看一个 TDD 会话的示例。想象一下,坐在办公桌前,快速勾勒出您认为的一项功能的高级设计,该功能允许用户登录应用程序并在忘记密码时更改密码。您决定从登录函数的第一个实现开始,创建一个类来处理登录过程的所有逻辑。您打开您最喜欢的编辑器并创建一个名为“空登录阻止用户登录”的单元测试。您编写单元测试代码,首先创建 Login 类的实例(您尚未创建)。然后,您编写代码来调用 Login 类中传递空用户名和密码的方法。最后,您根据预期结果编写检查,检查具有空登录名的用户实际上是否未登录。您正在尝试运行测试,但它甚至无法编译,因为您没有 Login 类。您可以修复这种情况,并创建一个 Login 类,并在该类中创建一个用于登录的方法,以及另一个用于检查用户状态以查看他们是否已登录的方法。到目前为止你还没有实现这个类的功能和我们需要的方法。此时您运行测试。现在它可以编译,但立即失败。
什么是 TDD 和单元测试 [翻译] - 12
现在您返回代码并实现功能以通过测试。在我们的例子中,这意味着我们应该得到结果:“用户未登录。” 您再次运行测试,现在它通过了。让我们继续下一个测试。现在让我们假设您需要编写一个名为“如果用户输入了有效的用户名和密码,则用户已登录”的测试。您编写一个单元测试来实例化 Login 类并尝试使用用户名和密码登录。在单元测试中,您编写一条语句,要求 Login 类应对用户是否登录的问题回答“是”。您运行这个新测试,当然它会失败,因为您的 Login 类始终返回用户未登录的信息。您返回到 Login 类并实现一些代码来验证用户是否已登录。在这种情况下,您必须弄清楚如何隔离该模块。目前,最简单的方法是对您在测试中使用的用户名和密码进行硬编码,如果它们匹配,则返回结果“用户已登录”。您进行此更改,运行两个测试,它们都通过了。让我们继续最后一步:查看生成的代码,并寻找重新组织和简化它的方法。所以TDD算法是:
  1. 创建了一个测试。
  2. 我们为此测试编写了代码。
  3. 重构了代码。

结论

什么是 TDD 和单元测试 [翻译] - 13
这就是我现阶段想告诉您的有关单元测试和 TDD 的全部内容。事实上,尝试隔离代码模块会遇到很多困难,因为代码可能非常复杂且令人困惑。很少有类是完全孤立存在的。相反,它们具有依赖关系,而这些依赖关系又具有依赖关系,等等。为了处理这种情况,TDD 老手使用模拟,它通过替换依赖模块中的对象来帮助隔离各个类。本文只是对单元测试和 TDD 的概述和稍微简化的介绍,我们不会详细介绍虚拟模块和其他 TDD 技术。这个想法是为您提供 TDD 和单元测试的基本概念和原则,希望您现在已经掌握。原文 - https://simpleprogrammer.com/2017/01/30/tdd-unit-testing/
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION