测试驱动开发 (TDD)和行为驱动开发 (BDD ) 都是软件开发的测试优先方法。它们共享共同的概念和范式,植根于相同的哲学。在本文中,我们将重点介绍这两种方法的共性、差异、优缺点。
什么是测试驱动开发(TDD)
测试驱动开发 (TDD) 是一种依赖于重复短开发周期的软件开发过程:需求转化为非常具体的测试用例。编写代码是为了使测试通过。最后,对代码进行重构和改进,以确保代码质量并消除任何技术债务。这个循环被称为 Red-Green-Refactor 循环。
什么是行为驱动开发 (BDD)
行为驱动开发 (BDD) 是一个软件开发过程,它鼓励参与项目交付的所有各方之间的协作。它鼓励以各方都能理解的通用语言对系统行为进行定义和形式化,并将此定义用作基于 TDD 的流程的种子。
TDD 和 BDD 之间的主要区别
TDD | BDD | |
重点 | 交付功能特性 | 交付预期的系统行为 |
方法 | 自下而上或自上而下(验收测试驱动开发) | 自顶向下 |
初始点 | 一个测试用例 | 用户故事/场景 |
参与者 | 技术团队 | 包括客户在内的所有团队成员 |
语言 | 编程语言 | 通用语 |
过程 | 精益,迭代 | 精益,迭代 |
提供 | 符合我们测试标准的功能系统 | 一个按预期运行的系统和一个用人类通用语言描述系统行为的测试套件 |
避免 | 过度工程、低测试覆盖率和低价值测试 | 偏离预期的系统行为 |
脆性 | 实现的变化可能导致测试套件的变化 | 如果需要更改系统行为,则仅需要更改测试套件 |
实施难度 | 自底向上比较简单,自顶向下比较难 | 为所有相关方提供更大的学习曲线 |
测试驱动开发 (TDD)
在 TDD 中,我们有众所周知的 Red-Green-Refactor 循环。我们从一个失败的测试开始(红色),并尽可能少地实现代码以使其通过(绿色)。此过程也称为测试优先开发。TDD 还增加了一个 Refactor 阶段,这对整体成功同样重要。
TDD 方法是由单元测试和后来的 TDD、敏捷软件开发和最终极限编程的先驱之一的 Kent Beck 发现(或可能重新发现)的。
下图很好地提供了一个易于理解的过程概述。然而,美在于细节。在深入研究每个单独的阶段之前,我们还必须讨论 TDD 的两种高级方法,即自下而上和自上而下的 TDD。
自下而上的 TDD
自底向上 TDD(也称为 Inside-Out TDD)背后的理念是迭代地构建功能,一次只关注一个实体,在移动到其他实体和其他层之前巩固其行为。
我们首先编写单元级测试,继续执行它们,然后继续编写高级测试,聚合低级测试的功能,创建所述聚合测试的实现,等等。通过逐层构建,我们最终将达到一个阶段,即聚合测试是一个验收水平测试,希望符合所要求的功能。此过程使其成为一种高度以开发人员为中心的方法,主要旨在使开发人员的生活更轻松。
优点 | 缺点 |
一次只关注一个功能实体 | 延迟整合阶段 |
功能实体易于识别 | 实体需要暴露的行为数量尚不清楚 |
不需要高水平的愿景开始 | 实体之间无法正确交互从而需要重构的高风险 |
有助于并行化 | 业务逻辑可能分布在多个实体中,使其不清楚且难以测试 |
自上而下的 TDD
自上而下的 TDD 也称为外向内 TDD 或验收测试驱动开发 (ATDD)。它采取相反的方法。我们开始构建一个系统,迭代地为实现添加更多细节。随着重构机会变得明显,迭代地将其分解为更小的实体。
我们首先编写一个可接受级别的测试,然后进行最少的实现。该测试也需要逐步进行。因此,在创建任何新实体或方法之前,需要在适当级别进行测试。因此,我们迭代地改进解决方案,直到它解决了启动整个练习的问题,即验收测试。
这种设置使自上而下的 TDD 成为一种更加以业务/客户为中心的方法。这种方法更难正确,因为它在很大程度上依赖于客户和团队之间的良好沟通。它还需要开发人员的良好公民身份,因为需要仔细考虑下一个迭代步骤。这个过程会及时加快,但确实有一个学习曲线。然而,好处远远超过任何负面影响。这种方法导致客户和团队之间的协作成为中心舞台,一个具有非常明确的行为、明确定义的流程、专注于首先集成以及非常可预测的工作流程和结果的系统。
优点 | 缺点 |
一次只关注一个用户请求的场景 | 获得正确的断言测试至关重要,因此需要业务/用户/客户和团队之间的协作讨论 |
流量很容易识别 | 依赖于 Stubbing、Mocking 和/或测试替身 |
重点是集成而不是实现细节 | 由于通过多次迭代识别流程,因此启动较慢 |
一个实体需要暴露的行为数量是明确的 | 更有限的并行化机会,直到骨架系统开始出现 |
用户需求、系统设计和实施细节都清楚地反映在测试套件中 | |
可预见的 |
红绿重构生命周期
有了上面讨论过的关于如何处理 TDD 的高级愿景,我们可以自由地深入研究 Red-Green-Refactor 流程的三个核心阶段。
红色的
我们首先编写一个测试,执行它(因此失败),然后才开始执行该测试。在这里编写正确的测试至关重要,就我们试图实现的测试层达成一致也是如此。这将是验收水平测试还是单元水平测试?这种选择是自下而上和自上而下 TDD 之间的主要界限。
绿
在绿色阶段,我们必须创建一个实现以使红色阶段中定义的测试通过。实现应该是尽可能最小的实现,使测试通过,仅此而已。运行测试并观察它通过。
创建尽可能最小的实现通常是这里的挑战,因为开发人员可能倾向于通过习惯的力量立即美化实现。这个结果是不可取的,因为它会产生技术包袱,随着时间的推移,这将使重构更加昂贵,并可能基于重构成本扭曲系统。通过使每个实施步骤尽可能小,我们进一步强调了我们试图实施的过程的迭代性质。此功能将赋予我们敏捷性。
另一个关键方面是红色阶段,即测试,是推动绿色阶段的动力。任何实现都不应该由非常具体的测试驱动。如果我们采用自下而上的方法,这很自然。但是,如果我们采用自上而下的方法,那么我们必须更加认真,并确保在实现形成时创建进一步的测试,从而从验收级别测试转向单元级别测试。
重构
重构阶段是 TDD 的第三个支柱。这里的目标是重新审视和改进实施。优化了实现,提高了代码质量,消除了冗余。
重构对许多人来说可能具有负面含义,被视为纯粹的成本,在第一次就修复了不正确的事情。这种看法源于更传统的工作流程,其中重构主要只在必要时进行,通常是在技术包袱达到无法维持的水平时,从而导致冗长、昂贵的重构工作。
然而,在这里,重构是工作流的固有部分,并且是迭代执行的。这种灵活性极大地降低了重构的成本。代码没有完全重做。相反,它正在缓慢发展。此外,根据定义,重构的代码包含在测试中。在之前的代码迭代中已经通过的测试。因此,可以放心地进行重构,从而进一步加快速度。此外,这种改进代码库的迭代方法允许紧急设计,这大大降低了过度设计问题的风险。
行为不应该改变是至关重要的,我们不会在重构阶段添加额外的功能。这个过程允许以极大的信心和敏捷性进行重构,因为根据定义,相关代码已经被测试覆盖。
行为驱动开发 (BDD)
如前所述,TDD(或自下而上的 TDD)是一种以开发人员为中心的方法,旨在生成更好的代码库和更好的测试套件。相比之下,ATDD 更加以客户为中心,旨在提供更好的整体解决方案。我们可以将行为驱动开发视为 ATDD 的下一个逻辑进展。Dan North在 TDD 和 ATDD 方面的经验导致他提出了 BDD 概念,其想法和主张是将 TDD 和 ATDD 的最佳方面结合在一起,同时消除他在这两种方法中发现的痛点。他发现,具有描述性的测试名称很有帮助,而且测试行为比功能测试更有价值。
Dan North 将 BDD 简洁地描述为“使用多个级别的示例来创建共享理解和表面确定性以交付重要的软件”,做得很好。
这里的一些关键点:
- 我们关心的是系统的行为
- 测试行为比测试特定的功能实现细节更有价值
- 使用通用语言/符号在领域专家、开发人员、测试人员、利益相关者等之间形成对预期和现有行为的共同理解。
- 当每个人都能够理解系统的行为、已经实现了什么以及正在实现什么并且保证系统满足所描述的行为时,我们实现了表面确定性
BDD 将责任更多地放在客户和团队之间富有成效的协作上。正确定义系统的行为变得更加关键,从而导致正确的行为测试。这里的一个常见陷阱是对系统将如何实施行为做出假设。这个错误发生在被实现细节污染的测试中,因此使其成为功能测试而不是真正的行为测试。这个错误是我们想要避免的。
行为测试的价值在于它测试了系统。它不关心它是如何实现结果的。这种设置意味着行为测试不应随时间而改变。除非行为本身需要作为功能请求的一部分进行更改。功能测试的成本效益更为显着,因为此类测试通常与实现紧密耦合,以至于代码的重构也涉及测试的重构。
然而,更大的好处是保留了表面确定性。在功能测试中,代码重构也可能需要测试重构,这不可避免地会导致失去信心。如果测试失败,我们不确定原因可能是什么:代码、测试或两者兼而有之。即使测试通过,我们也不能确信之前的行为已经被保留。我们所知道的是测试与实现相匹配。这个结果的价值很低,因为最终客户关心的是系统的行为。因此,我们需要测试和保证的是系统的行为。
基于 BDD 的方法应该导致完整的测试覆盖,其中行为测试使用通用语言向所有各方充分描述系统的行为。与功能测试相比,即使完全覆盖也不能保证系统是否满足客户的需求,重构测试套件本身的风险和成本只会随着覆盖率的增加而增加。当然,通过自上而下地从行为测试到更多功能测试来利用这两种方法,将为行为测试带来表面确定性的好处。此外,功能测试以开发人员为中心的好处也抑制了功能测试的成本和风险,因为它们只在适当的地方使用。
在直接比较 TDD 和 BDD 时,主要变化是:
- 简化了测试什么的决定;我们需要测试行为
- 我们利用一种通用语言来缩短另一层通信并简化工作;利益相关者定义的用户故事是测试用例
出现了框架和工具的生态系统,以允许跨团队进行基于通用语言的协作。以及通过利用行业标准工具来集成和执行测试等行为。这方面的例子包括 Cucumber、JBehave 和 Fitnesse 等等。
适合工作的工具
正如我们所看到的,TDD 和 BDD 之间并没有真正的直接竞争。将 BDD 视为 TDD 和 ATDD 的进一步发展,它带来了更多以客户为中心,并进一步强调了客户与技术团队在流程的所有阶段之间的沟通。这样做的结果是一个系统,其行为符合所有相关方的预期,以及一个测试套件,以人类可读的方式描述系统的许多行为,每个人都可以访问并易于理解。反过来,该系统不仅对已实施的系统,而且对系统的未来更改、重构和维护提供了非常高的信心。
同时,BDD 很大程度上基于 TDD 流程,并进行了一些关键更改。虽然客户或团队的特定成员可能主要参与系统的最顶层,但其他团队成员(如开发人员和 QA 工程师)会在他们以自己的方式在顶层工作时从 BDD 有机地转变为 TDD 模型。下来时尚。
我们期待以下主要好处:
- 把痛苦向前推进
- 客户和团队之间协作的责任
- 客户和团队领导之间共享的共同语言,以分享理解
- 实施精益的迭代过程
- 保证交付的软件不仅可以正常工作,而且可以按定义工作
- 通过紧急设计避免过度设计,从而通过尽可能最小的解决方案达到预期的结果
- 表面确定性允许快速和自信的代码重构
- 测试具有与生俱来的价值 VS 创建测试只是为了满足任意代码覆盖率阈值
- 测试是完整描述系统行为的活文档
在某些情况下,BDD 可能不是合适的选择。在某些情况下,所讨论的系统技术性很强,可能根本不面向客户。它使需求与功能的联系比与行为的联系更紧密,从而使 TDD 可能更适合。
采用 TDD 还是 BDD?
最终,问题不应该是采用 TDD 还是 BDD,而是哪种方法最适合手头的任务。很多时候,这个问题的答案是两者兼而有之。随着越来越多的人参与到更重要的项目中,不言而喻的是,在整个项目生命周期的不同级别和不同时间都需要这两种方法。TDD 将为技术团队提供结构和信心。BDD 将促进和强调所有相关方之间的沟通,并最终交付满足客户期望的产品,并提供所需的表面确定性,以确保对未来进一步发展产品的信心。
通常情况下,这里没有灵丹妙药。相反,我们拥有的是一些非常有效的方法。两者的知识将使团队能够根据项目的需要确定最佳方法。进一步的经验和执行的流畅性将使团队能够在整个项目生命周期中根据需要使用其工具箱中的所有工具,从而实现最佳的业务成果。