TDD与BDD有什么区别?详细示例介绍

2021年11月10日17:16:42 发表评论 1,165 次浏览

天,我们为你带来了另一个与测试相关的概念。好吧,我们带来了两个:TDD(测试驱动开发)和 BDD(行为驱动开发)。这是两种广为人知的软件开发技术,其中自动化测试起着核心作用。

但人们常常对这两个概念感到困惑。“TDD与BDD有什么区别?” 是一个经常被问到的问题。

TDD与BDD有什么不同?在今天的帖子中,我们将为这场辩论做出我们的贡献。总之,这就是你将学到的:

  • 什么是 TDD
  • TDD 工作流程
  • 一些不同类型的 TDD
  • JavaScript 中的 TDD 示例
  • 什么是 BDD
  • BDD 工作流程
  • TDD与BDD对比示例
  • BDD 示例,同样使用 JavaScript

在那次转移之后,我们将两者结合在一起,总结它们之间的异同。让我们开始吧。

TDD:快速定义

TDD 代表测试驱动开发。这是一种软件开发技术,我们在编写生产代码之前先编写测试。然后,开发人员必须编写使测试通过所需的最少代码量。我们的想法是,通过严格遵循此工作流程,测试将引导应用程序设计到​​更易于维护的状态。

这怎么可能?

正如你接下来将看到的,我们在执行 TDD 时编写的自动化测试类型具有一些有趣的特性,其中最重要的是它们对绝对(或尽可能接近)隔离的要求。当你进行 TDD 时,你只会编写代码来响应失败的测试。这导致代码尽可能没有依赖和耦合。

这导致更简单、更好的设计,代码更易于维护和发展。

TDD 是关于单元测试的

关于 TDD,当我们谈论测试时,我们指的是单元测试。

TDD与BDD有什么不同?如果你熟悉称为测试金字塔的概念,那么你就会知道单元测试是最重要的自动化测试类型之一。单元测试是完全隔离地测试代码库的一小部分(即单元)的小型测试。我们也可以说单元测试不应该产生或消耗副作用。

这意味着单元测试不能与代码外部的事物交互,例如数据库、文件系统、网络,甚至系统时钟。

这也是一种单元测试最佳实践,其中的测试不相互依赖,也不需要以任何给定的顺序执行。换句话说:理想情况下,单元测试是完全独立的并且与任何外部问题隔离。在编写测试与这些外部问题交互的代码区域时,你通常必须依赖诸如 stubbing 和mocking 之类的技术。

单元测试 = 隔离测试

想到的测试中隔离的首要好处之一就是速度。由于单元测试不涉及我们刚刚提到的内容,因此它们比与这些依赖项交互的测试(例如集成测试)运行得更快。

其他类型的测试可能需要复杂且容易出错的设置过程,包括准备环境、创建或编辑配置文件或复制数据库等步骤。另一方面,单元测试通常可以在很少或根本没有设置的情况下编写。

TDD与BDD有什么区别?详细示例介绍

但是单元测试隔离最重要的好处与确定性有关。一个好的单元测试是确定性的。也就是说,如果它失败了,它应该继续失败,直到代码发生变化——测试本身或被测系统。反之亦然:如果测试通过,它应该继续通过,直到测试或正在测试的代码发生变化。

需要测试单元隔离以确保确定性。如果单元测试依赖于外部系统,它可能会由于这些系统之一的问题而失败。例如,依赖于数据库的测试可能会因网络中断而失败,从而阻止成功连接到服务器。

TDD 工作流

TDD与BDD有什么区别?Robert C. Martin(又名 Uncle Bob)使用他所谓的 TDD 三个定律来描述 TDD:

  1. 除非要使失败的单元测试通过,否则不允许编写任何生产代码。
  2. 不允许编写任何足以导致失败的单元测试;和编译失败是失败。
  3. 不允许编写任何足以通过一个失败的单元测试的生产代码。

TDD与BDD对比示例:以上三个简单的规则定义了 TDD 的整个工作流程。但是,我们将以更精简的方式对其进行解释。

第 1 步:编写一个失败的测试

TDD 工作流的第一步是为你将要创建的功能部分编写单元测试。你将编写的测试必然是失败的测试,因为你正在尝试测试甚至不存在的东西。换句话说,你将调用尚未创建的类/方法/函数。这将在执行测试时导致运行时错误。

在诸如 C# 或 Java 之类的编译语言中,代码甚至无法编译。

第 2 步:执行所有现有测试

下一步是执行所有正在执行的测试。此步骤至关重要,因为确保所有现有测试(如果有)都通过至关重要。运行测试后,只有你最近编写的测试应该会失败。

第 3 步:编写生产代码以使测试通过

在此阶段,你编写尽可能少的代码以使测试通过。换句话说:此时你不一定要尝试解决问题。相反,你的目标应该只是让测试通过。你不仅可以“作弊”——例如,通过返回一个硬编码的值。我们鼓励你这样做

编写生产代码后,是时候再次执行测试了。如果你最近编写的测试仍未通过,则必须返回步骤 #3 并更改生产代码以确保它通过。

第 4 步:必要时重构

最后,我们进入最后一个可选步骤,你重构代码以删除代码重复并总体上改进代码。

TDD 有不同的风格

TDD与BDD对比示例:需要强调的是,当涉及到 TDD 时,有不同的风格甚至不同的思想流派。可以使用各种名称来引用这些不同的样式。出于本文的目的,我们将简要介绍与 TDD 与 BDD 主题相关的两大类 TDD。

由内而外的 TDD

我们将在此处介绍的第一种 TDD 方法是由内而外的 TDD,也称为自底向上 TDD,或芝加哥 TDD 学院。顾名思义,这种方法的工作原理是从应用程序的内部开始,然后找出解决方法。

在练习这种形式的 TDD 时,你首先要编写较低级别的单元测试。然后,你慢慢地进步到越来越高的层次,直到你到达验收测试层。

以下是由内而外方法的一些优点:

  • 从一开始就进行全面的测试和覆盖。
  • 并行开发,即开发人员可以独立地处理不同的功能,而不需要太多的协调。

对于缺点:

  • 测试与低级实现细节的高耦合。
  • 它可能过于以开发人员为中心,导致代码无法解决用户的实际需求。

由外向内 TDD

TDD与BDD有什么区别?外向内 TDD 也称为自顶向下 TDD。它有时被称为伦敦 TDD 学院或验收测试驱动开发 (ATDD)。

顾名思义,这种方法与前一种方法相反。你从系统的外部开始并构建你的方式应用程序。与前一种方法相比,这种方法不太以开发人员为中心。与其考虑低级实现细节,你更关心给定的功能作为一个整体是如何工作的。

在这种方法中,你从验收测试级别开始,然后通过将需求分解为更小的单元来逐步深入。

以下是这种方法的一些好处:

  • 测试用例与内部细节的耦合较少,导致测试套件不那么脆弱。
  • 由于不以开发人员为中心,你更有可能最终解决用户的需求。

对于缺点:

  • 这种方法可能会使并行开发变得困难。
  • 这种方法可能需要更多的模拟/存根,以替换尚未准备好的依赖项。

JavaScript 中的简短 TDD 示例

TDD与BDD对比示例:我们现在将看到一个快速的 TDD 示例,使用 JavaScript 作为语言。在我们的示例中,我们将开始求解Roy Osherov 开发的字符串计算器 Kata

这是我们的第一个测试:

var assert = require('assert');
var calc = require('../src/calculator.js')

describe('StringCalculator', function() {
  describe('#add()', function() {

    var calc = new StringCalculator();

    it('should return 0 when passing an empty string', function() {
      assert.equal(calc.add(''), 0);
    });
  });
});

测试超级简单。它需要calculator.js 文件,该文件存在,但它是空的。然后,当我们传递一个空字符串时,它会验证 add 函数是否真的返回 0。由于我们正在尝试测试不存在的代码,因此该测试将失败。

下一步是使测试通过。在此阶段,你无需尝试获得最终解决方案。相反,做最简单的事情来让测试通过。这是 src/calculator.js 的代码:

StringCalculator = function() {};

StringCalculator.prototype.add = function(stringNumbers) { return 0; }

如你所见,我们只返回 0,这使得测试通过。

TDD与BDD有什么不同?下一阶段是重构步骤,这是可选的。在这里,我们主要做两件事:

  • 重构我们认为合适的代码(例如,删除重复,提高可读性或效率)
  • 再次运行测试,它们应该都通过了。

在示例中,我们没有太多的改进空间,所以这次没有重构。

BDD:快速定义

Dan North在 2006 年的一篇文章中介绍了 BDD 的概念。BDD 是一个首字母缩写词,代表行为驱动开发。根据你刚刚了解的关于 TDD 的知识,你应该能够猜到它与 TDD 类似,但是开发不是由测试驱动的,而是由行为驱动的。但是这里的“行为”到底是什么意思呢?

我们可以将行为视为应用程序应该……好吧,行为,以满足某些要求以满足用户需求的方式。

TDD与BDD有什么区别?详细示例介绍

BDD 是 TDD 的一种扩展或演变,它不会被实现细节和技术问题所困扰,而是专注于更高级别的问题,例如业务需求。这也是关于沟通的。它最重要和最有趣的特性之一是,它旨在鼓励开发人员、QA 专业人员和业务分析师之间的协作,通过提供一种内聚的、无处不在的语言,每个人都可以使用它来编写和阅读应用程序的需求。

BDD 通过使用简单的英语要求作为测试的起点来实现这一点,允许和鼓励非技术参与者进行协作。

BDD工作流程

让我们看一个 BDD 工作流的例子。

第 1 步:编写行为

第一步是编写我们想要创建的行为。理想情况下,业务人员(例如产品所有者或客户代表或业务分析师)应该使用 Given/When/Then 模板(稍后详细介绍)用英语编写行为。

第 2 步:将简单的英语行为转换为脚本

下一步是根据用简单英语编写的行为/要求编写编程测试。

第 3 步:通过编写生产代码实现行为

在这一步,我们通过编写生产代码来实现所需的功能,然后运行行为以验证一切是否正确。如果不是,我们会更改生产代码,直到行为测试通过。

第 4 步:根据需要重构

然后我们有最后的、可选的重构步骤,与 TDD 相同。这个阶段是整理代码、去除重复、不必要的复杂性、提高可读性等的机会。

JavaScript 中的 BDD 示例

是时候介绍我们的 BDD 示例了。正如我们在 TDD 示例中所做的那样,我们将逐步完成工作流程步骤,展示我们在实践中如何执行这些步骤。对于 BDD 示例,我们将使用Cucumber

我们将要使用的示例将基于你可以在Cucumber 文档站点上找到的“10 分钟教程” 。开始了。

第 1 步:编写行为

TDD与BDD对比示例:在 Cucumber 中,你使用场景来描述行为。考虑以下代码:

Feature: Is it Friday yet?
  Everybody wants to know when it's Friday

  Scenario: Sunday isn't Friday
    Given today is Sunday
    When I ask whether it's Friday yet
    Then I should be told "Nope"

在这里,我们定义了一个名为“现在是星期五吗?”的功能。接着是说明。然后我们使用 Given-When-Then 语法定义场景。该场景简单地定义了,如果今天是星期日,而我问是否是星期五,答案应该是否定的。

下一步是将用人类可读的英语编写的场景转换为可执行的测试。以下代码摘录显示了我们场景步骤的编码:

const assert = require('assert');
const { Given, When, Then } = require('cucumber');

const isItFriday = today => {
  return 'Nope';
}

Given('today is Sunday', () => {
  this.today = 'Sunday';
});
When('I ask whether it\'s Friday yet', () => {
  this.actualAnswer = isItFriday(this.today);
});
Then('I should be told {string}', expectedAnswer => {
  assert.equal(this.actualAnswer, expectedAnswer);
});

如你所见,我们只是对“isItFriday”常量的响应“nope”进行了硬编码。即使它没有完全解决问题,它也会使测试/行为通过。在这一步,步骤应该通过。

TDD 与 BDD:现在一起

TDD与BDD有什么区别?是时候考虑我们在这篇文章中看到的所有内容并得出结论了。我们将首先介绍每种方法的主要优点,从 TDD 开始。然后,我们将讨论 TDD 和 BDD 之间的异同。然后,我们对辩论做出判断。让我们开始吧。

TDD 主要优势

如前所述,TDD 和 BDD 都不是测试方法。相反,它们是软件开发/设计方法,其中自动化——单元——测试起着至关重要的作用。

话虽如此,两种方法的使用最终都会为你的测试方法产生积极的结果。我们现在将总结采用 TDD 可以获得的一些主要好处。

  • 更简单的设计。TDD 工作流鼓励开发人员只编写所有步骤所需的最少量代码。这导致了一个简单、简约的设计,不会产生不必要的耦合和依赖,这使得代码更易于导航、维护和重构。
  • 更少的缺陷。使用 TDD,你可以尽早进行测试,从而减少将其投入生产的错误。
  • 详细的项目规范。单元测试可以为你的项目扮演详细文档的角色,并且使用 TDD,你可以确保它尽可能全面。
  • 测试覆盖率高。 测试覆盖率意味着确保你测试应用程序中需要测试的功能。使用 TDD,默认情况下你会得到它,因为你在编写实际代码之前编写测试。
  • 代码覆盖率高。 代码覆盖率是一种更为人所知的指标,它指的是代码库中至少由一个单元测试执行的行或路径的比率。使用 TDD,默认情况下你可以获得 100% 的单元测试代码覆盖率。
  • 更少的回归问题。你通过采用 TDD 服务器作为安全网获得的综合单元测试套件,保护你的应用程序免受回归问题的影响。

BDD 主要优势

TDD与BDD有什么不同?BDD 分享了 TDD 的一些好处,同时也有一些独有的好处。现在让我们总结一下:

  • 合作。BDD 的主要价值主张之一是通过让技术和非技术专业人员参与规范的创建来促进组织内部的协作。
  • 更少的缺陷。与 TDD 一样,采用 BDD 的团队也获得了左移测试的好处,其中包括在将其一直用于生产之前发现并修复更多缺陷。
  • 更少的回归问题。BDD 还产生了一个全面的测试套件,可以保护应用程序免受回归。
  • 适合多个目标受众的综合项目文档。虽然你为 TDD 编写的单元测试可以被视为文档,但只有开发人员才能阅读它们。另一方面,BDD 场景应该被每个人编写和理解。因此,虽然这两种方法都生成了全面的规范,但只有 BDD 允许组织中的每个人都利用该规范。
  • 每个功能的可追溯性。你在执行 BDD 时编写的可执行测试应该链接回它们所基于的场景。这样,你就可以获得完整的可追溯性:从任何测试开始,你都可以找出产生它的要求是什么。

TDD 与 BDD:异同

TDD与BDD有什么区别?我们现在将总结这两种方法之间的主要区别和相似之处。

让我们从头开始。在 TDD 中,该过程从编写失败的测试用例开始。在 BDD 中,你可以通过用简单易懂的英语编写场景来启动流程。

TDD 和 BDD 都不是测试方法——这是一个常见的误解。它们是围绕自动化测试的软件开发方法。

这两种方法都实现了拥有涵盖应用程序的全面自动化测试套件的结果,尽管这对于 TDD 来说肯定更为明显。

这两种技术之间最明显的区别可能是制作和使用测试的人员的不同。TDD 只需要开发人员的协作。即使非技术人员想要协作,他们也可能无法协作,因为阅读 TDD 生成的单元测试需要编程知识。

另一方面,BDD 不仅鼓励开发人员的协作,还鼓励 QA 人员、业务分析师等的协作。

TDD 与 BDD:主要差异,总结

TDD与BDD有什么不同?

TDDBDD
TDD 更关心“如何”。某个功能是如何实现的? BDD 更关心“什么”。从用户的角度来看,最终结果是什么? 
TDD 更“以开发人员为中心”:该过程从编写失败的单元测试开始。BDD 更以最终用户为中心:该过程首先编写一个场景,描述应用程序在给定情况下的行为方式。
TDD 过程会生成只有开发人员才能阅读的详细文档。BDD 流程以简单的英语生成详细的规范,每个利益相关者都可以阅读。
只有开发人员为 TDD 过程做出贡献。BDD 鼓励并支持非技术利益相关者的协作。

最终裁决

最后,判决。“TDD 与 BDD”辩论的答案是什么?有没有最好的方法?

答案是没有一个一刀切的答案。在某些情况下,TDD 比 BDD 更容易指示,反之亦然。

BDD 可能是操作由用户行为决定的应用程序的最佳方法。另一方面,对于库或 RESTful API 之类的东西,TDD 可能是最合适的技术。

总结

TDD与BDD有什么区别?在这篇博文中,我们研究了“TDD 与 BDD”的争论,定义了这两种方法中的TDD与BDD对比示例。到现在为止,你应该对这两种方法的基本原理有了深入的了解。现在,阅读更多关于技术和实践的信息。

木子山

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: