TDD 与 BDD:主要区别、以两种方式构建的一个功能以及何时使用每种方式

  1. 首页
  2. 博客
  3. 测试自动化

测试自动化

TDD 与 BDD:主要区别、以两种方式构建的一项功能以及何时使用每种方式

TDD 帮助开发人员设计正确的代码。 BDD 帮助整个团队就软件应该做什么达成一致。查看使用每种方法构建的相同折扣功能,并了解两者如何协同工作。

阅读约 9 分钟
TDD 与 BDD:主要区别、一种功能可以通过两种方式构建以及何时使用每种方式

要点速览

  • TDD 意味着首先编写失败的测试,然后编写足够的代码来通过,然后进行整理。
  • BDD 用共享语言描述行为,通常为“Given”、“When”、“Then”。
  • TDD主要指导代码设计。 BDD 主要建立共享理解。
  • 他们不是竞争对手,许多团队使用 BDD 来描述功能,使用 TDD 来描述下面的细节。

TDD 与 BDD 的争论常常被认为是选边站。事实上,他们回答不同的问题。测试驱动开发询问代码是否符合开发人员的预期,而行为驱动开发询问团队是否同意软件应该做什么。

为了使差异具体化,我们为每种方法构建了一个小的折扣功能。我们使用标准库在 Python 3.13 上运行代码,下面的结果来自该运行。 BDD 部分使用我们自己的小型运行程序来展示该机制,因此实际项目将使用 Cucumber 等工具或Behavior 来代替。

简短的回答

测试驱动开发(TDD) 是一种编码实践。您编写一个小的失败测试,​​用最简单的代码使其通过,然后改进设计。测试主要由开发人员用代码编写。肯特·贝克推广了这种做法。

行为驱动开发 (BDD) 是一种源自 TDD 的协作实践,由 Dan North 引入了该术语。开发人员、测试人员和业务人员就系统应如何运行的示例达成一致,用简单的语言编写,然后可以将这些示例自动化。

TDD 与 BDD 对比

方面 时分双工 BDD
主要焦点 正确、设计良好的代码 对行为的共同理解
写于 编程语言 简单的语言,例如小黄瓜
撰写者 开发商 业务、QA 和开发人员一起
可读者 开发商 团队中的每个人
典型水平 单位 特征或接受
起点 失败的测试 举例对话
循环 红、绿、重构 发现、制定、自动化
常用工具 pytest、JUnit、玩笑 黄瓜、行为、SpecFlow、Reqnroll

特点

以下是我们将实施的业务规则: 1000 件或以上的订单可享受 10% 的折扣。会员订购这些订单可享受额外 5% 的折扣。

这听起来似乎很清楚,但实际上已经很模糊了。会员消费满500元可以享受5折吗?该句子说“那些命令”,所以我们假设不是。请记住这一点,因为这正是 BDD 旨在尽早提出的问题。

使用 TDD 构建它

TDD 遵循一个称为 红色、绿色、重构 的短循环:

步骤 你做什么
红色 为尚不存在的行为编写一个测试,然后观察它失败
绿色 编写最简单的代码使其通过
重构 改进代码,保持测试绿色

这些是我们的测试,每条规则都有一个测试:

class DiscountTests(unittest.TestCase):
    def test_no_discount_below_1000(self):
        self.assertEqual(discount(999, False), 0)

    def test_ten_percent_from_1000(self):
        self.assertEqual(discount(1000, False), 100)

    def test_member_gets_extra_five_percent(self):
        self.assertEqual(discount(1000, True), 150)

然后,我们分三个阶段编写代码,并在每个阶段之后运行测试。严格的 TDD 会话一次添加一个测试,这显示了相同的进展:

第一阶段:最简单的事情

def discount(total, is_member):
    return 0

# 3 tests, 2 failed
#   test_member_gets_extra_five_percent
#   test_ten_percent_from_1000

返回零看起来很愚蠢,但它证明测试实际上可能会失败,并且它通过的一个测试是“无折扣”规则。

第二阶段:添加阈值

def discount(total, is_member):
    if total >= 1000:
        return total * 0.1
    return 0

# 3 tests, 1 failed
#   test_member_gets_extra_five_percent

第 3 阶段:添加成员并清理

def discount(total, is_member):
    rate = 0.1 if total >= 1000 else 0
    if is_member:
        rate += 0.05
    return round(total * rate, 2)

# 3 tests, 0 failed

三人均通过。但仔细阅读这个版本。它给消费满 500 美元的会员提供 5% 的折扣,这不是我们假设的规则的含义。没有测试涵盖该案例,因此没有任何标记。 TDD 仅保护您想要测试的行为。 这是一个真正的限制,也是关于示例的对话如此重要的原因。

为什么周期很重要

  • 红色证明测试有效。 从未失败的测试可能没有测试任何内容。
  • 绿色让您保持专注。 您只编写需要的代码,这有助于防止过度设计。
  • 重构保持高品质。测试让您有信心安全地更改设计。
  • 它影响设计。 难以测试的代码通常耦合得太紧密,您会立即注意到。

我们的 pytest 教程 展示了一种更方便的方法来编写此类测试,边界值分析 解释了为什么我们选择 999 和 1000 作为值。

使用 BDD 构建它

BDD 从对话开始,而不是从工具开始。通常这是一个被称为三人组的简短会议,产品人员、开发人员和测试人员讨论具体示例:

Tester:  What about a member spending 500?
Product: Good question. No discount. It has to reach 1000 first.
Dev:     And exactly 1000 counts?
Product: Yes, 1000 or more.

那个交换会发现我们的 TDD 代码中的问题。然后,他们以一种名为 Gherkin 的结构化格式记录示例:

Feature: Order discounts

  Scenario: Member reaches the discount threshold
    Given a member with a cart worth 1000
    When they view the amount to pay
    Then the amount to pay is 850

  Scenario: Member below the threshold gets no discount
    Given a member with a cart worth 500
    When they view the amount to pay
    Then the amount to pay is 500
关键词 目的
给定 起始情况
当 动作或事件
然后 预期结果

团队中的任何人都可以阅读该内容并说“是的,这是正确的”或“不,这是错误的”。即使您从未将其自动化,该场景也已经完成了有用的工作。

将步骤连接到代码

工具将每个简单语言行连接到一个函数,称为步骤定义。为了展示这个想法,我们自己编写了一个小版本并运行了第一个场景:

@step("a member with a cart worth 1000")
def given(ctx):
    ctx["cart"] = Cart(is_member=True)
    ctx["cart"].add(1000)

@step("they view the amount to pay")
def when(ctx):
    ctx["amount"] = ctx["cart"].payable()

@step("the amount to pay is 850")
def then(ctx):
    assert ctx["amount"] == 850

# ok Given a member with a cart worth 1000
# ok When they view the amount to pay
# ok Then the amount to pay is 850

真正的工具会添加参数,以便单个步骤可以匹配“1000”和“500”,以及报告。结果是活生生的文档:对系统的简单描述,如果不再真实,就会大声失败。

提示:BDD的价值在于对话。在代码完成后由一个人单独编写 Gherkin 的团队将获得该工具的成本,而几乎没有任何好处。

写出好的场景

弱 更好
当我单击 ID 为“btn-77”的按钮时 当我应用优惠券时
那么结果是正确的 那么支付的金额是850
一个场景二十个步骤 每个场景一种行为
描述屏幕 描述商业意图

描述点击和字段 ID 的场景在每次重新设计时都会中断,商业读者不再阅读它们。将用户界面详细信息保留在步骤代码中。这些场景与测试用例与测试场景中的示例非常相似,并且它们也经常用于用户验收测试。

两者一起使用

两者自然地适合“由外向内”的工作流程:

  1. 团队就行为达成一致,并编写了 BDD 场景。它失败了,因为什么都不存在。
  2. 开发人员使用 TDD 来构建所需的小型单元。
  3. 单元测试通过了,最终场景也通过了。
  4. 一切都在您的 CI/CD 管道 中运行。

将每一个都保持在正确的水平。您应该为重要行为提供一些场景,并为细节提供许多快速单元测试。我们的单元测试与集成测试指南涵盖了平衡。

工具

语言 TDD(单元测试) BDD
蟒蛇 py测试、单元测试 行为,pytest-bdd
JavaScript 开玩笑,维泰斯特 Cucumber.js
爪哇 JUnit、测试NG Cucumber-JVM
.NET xUnit、NUnit Reqnroll、SpecFlow

何时使用每个

TDD 非常适合以下情况:

  • 您正在编写复杂的逻辑,例如定价、解析或权限。
  • 您正在修复一个错误,因为您可以先通过失败的测试来重现它。
  • 您正在编写经常更改的代码。

BDD 非常适合以下情况:

  • 业务规则很复杂,误解的代价也很高。
  • 非技术人员确实会阅读并讨论这些场景。
  • 多个团队需要对某些行为的行为有一个共同的定义。

在以下情况下,这两种方法都可能太过分了:您正在编写一次性原型或探索不明确的技术想法。许多开发人员首先进行峰值,然后通过测试重写。如果工程之外没有人会关注这些场景,那么 BDD 工具也是一项糟糕的投资。在这种情况下,具有良好名称的简单测试会更简单。

常见错误

  • 将BDD视为测试工具。没有协作的Cucumber只是一种冗长的测试编写方式。
  • 测试 TDD 中的实现。与私有细节相关的测试在每次重构期间都会中断。
  • 跳过重构步骤。您可以在混乱的代码上通过测试。
  • 在代码之后编写测试并称之为 TDD。这仍然有用,但你会失去设计反馈。
  • 编写了太多 UI 场景。 这些场景变得缓慢且不稳定。请参阅片状测试。
  • 追逐覆盖率数字。 良好的覆盖率仍然会遗漏没有人考虑到的行为,正如我们的示例所示。

最后的话

TDD 帮助您构建正确的事物,而 BDD 帮助您构建正确的事物。我们的折扣示例展示了双方:代码通过了所有测试,但简单的对话揭示了错误的假设。在你的下一个小功能上尝试一下。在编写任何代码之前,与同事一起编写三个具体示例,然后一次构建一个失败的测试。

常见问题

TDD 和 BDD 之间的主要区别是什么?

TDD 专注于开发人员在实施之前编写失败的代码测试。 BDD 专注于整个团队使用通俗易懂的语言示例就行为达成一致。

TDD 和 BDD 可以一起使用吗?

是的,很多团队都这样做。 BDD 场景从外部定义功能,TDD 驱动其下方的较小代码单元。

BDD 比 TDD 更好吗?

总体而言,两者都不是更好,因为它们解决了不同的问题。 BDD 有助于共享理解,TDD 有助于代码设计和快速反馈。

小黄瓜是什么?

Gherkin 是一种用于编写场景的结构化简单语言格式,使用诸如“Given”、“When”和“Then”等关键字。像 Cucumber 这样的工具可以运行它。

TDD 会减慢开发速度吗?

一开始可能会感觉较慢,尤其是在学习时。团队通常可以通过更少的错误和更安全的更改来重新获得时间,尽管效果因项目而异。

继续学习

与Stack Quora合作

需要测试、WordPress 或 SEO 方面的帮助?

我们帮助团队交付更好的软件并发展他们的网站。告诉我们您正在做什么,我们将建议明确的下一步。

继续阅读

更多指南 测试、人工智能和现代开发

喜欢这篇文章吗?选择一个主题并直接跳至我们最新的教程。

人工智能

人工智能幻觉解释:语言模型为何会产生幻觉以及如何减少它

人工智能幻觉解释:它们是什么,为什么语言模型产生它们,常见类型,如何通过基础和提示来减少它们,以及我们进行的简单检查。

阅读文章 →
DevOps

环境变量解释:.env 文件、秘密和配置正确完成

使用我们运行的 Python 示例解释环境变量:读取和设置它们、为什么每个值都是字符串、.env 文件、进程继承、秘密和验证。

阅读文章 →
软件开发

API 速率限制解释:算法、标头和处理 429 错误

API 速率限制通过我们运行的模拟进行了解释:令牌桶、固定窗口、滑动窗口、429 响应、速率限制标头、带回退的重试和测试限制。

阅读文章 →
测试自动化

Python 中的模拟:unittest.mock、补丁以及导致测试谎言的错误

使用unittest.mock 在Python 中进行模拟,经过测试:模拟和return_value、side_effect、修补使用名称的位置、spec 和autospec、模拟日期以及常见错误。

阅读文章 →
软件测试

代码覆盖率解释:行覆盖率与分支覆盖率以及 100% 的真正含义

用真实的coverage.py运行来解释代码覆盖率:行、分支和条件覆盖率,阅读报告,为什么100%覆盖率仍然会错过错误,以及合理的目标。

阅读文章 →
数据库与 SQL

SQL 中的 DELETE、TRUNCATE 和 DROP:差异、回滚以及何时使用它们

DELETE、TRUNCATE 和 DROP 通过经过测试的 SQL 进行了解释:各自删除什么、回滚、触发器、身份重置、速度,以及 PostgreSQL、MySQL、SQL Server 和 SQLite 有何不同。

阅读文章 →
数据库与 SQL

SQL 视图解释:创建、使用和避免陷阱(附示例)

SQL 视图通过我们运行的查询进行解释:创建视图、实时结果、隐藏列、性能、可更新和物化视图、破坏的依赖关系和最佳实践。

阅读文章 →
软件开发

TypeScript 接口与类型:真正的差异,使用编译器进行测试

TypeScript 接口与类型,使用真实编译器进行检查:两者都可以做什么,只有一个可以做什么,声明合并,扩展与交集以及要遵循的简单规则。

阅读文章 →
软件开发

Python *args 和 **kwargs 解释:灵活的函数和清晰的示例

Python *args 和 **kwargs 用我们运行的代码进行了解释:打包和解包、参数顺序、仅关键字和仅位置参数、装饰器和常见错误。

阅读文章 →
软件开发

Python Lambda 函数:语法、实际用途以及何时不使用它们

Python lambda 函数用我们运行的代码进行了解释:语法、使用键排序、映射和过滤器、闭包、后期绑定陷阱,以及何时普通 def 是更好的选择。

阅读文章 →
人工智能

什么是人工智能代理?它们是如何工作的,只需运行一个简单的代理循环

什么是人工智能代理?关于代理循环、工具、内存和护栏的简单指南,以及我们运行的一个小型 Python 循环、真正的风险以及何时简单的聊天机器人是更好的选择。

阅读文章 →
DevOps

监控与可观察性:真正的区别,包括日志、指标和跟踪

监控与可观察性解释:已知和未知的故障、日志、指标和跟踪、检查事件演练、黄金信号、OpenTelemetry 和有用的警报。

阅读文章 →
DevOps

Docker Volumes 与 Bind Mounts:以正确的方式保留容器数据

Docker 卷与绑定挂载解释:为什么容器数据消失、命名卷、绑定挂载、tmpfs、Compose 示例、备份、权限以及何时使用它们。

阅读文章 →
测试自动化

TDD 与 BDD:主要区别、以两种方式构建的一个功能以及何时使用每种方式

TDD 与 BDD 的解释是通过一种双向构建的折扣功能:使用我们运行的代码进行红绿重构、Given-When-Then 场景、工具、权衡以及何时使用每种方案。

阅读文章 →
软件开发

Git merge 与 rebase 的区别:示例及如何选择

用真实的 log 输出、对比表、rebase 黄金法则和安全的团队工作流,讲清 Git merge 与 rebase 的区别。

阅读文章 →
软件开发

Python 列表推导式:语法、示例与何时避免使用

Python 列表推导式详解:语法、过滤、if-else、嵌套循环、实测耗时,以及何时普通 for 循环更合适。

阅读文章 →
数据库与 SQL

SQL JOIN 详解:附示例(INNER、LEFT、RIGHT、FULL)

通过示例表和查询,一步步讲解 SQL JOIN:INNER、LEFT、RIGHT、FULL、CROSS 和 SELF,并介绍常见错误与性能技巧。

阅读文章 →