”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > TDD什么时候有意义?

TDD什么时候有意义?

发布于2024-11-01
浏览:718

When does TDD make sense?

在我的职业生涯中,我经常听说测试驱动开发(TDD)是构建软件的有效方法。然而,我很长一段时间都很难看到好处。最近,当我从事一个非常适合 TDD 的项目时,这种情况发生了变化。在这种情况下,它显着改进了我的开发流程,使其更快并且更不容易出错。在本文中,我将解释何时使用 TDD 以及为什么它在某些场景下效果最好。

当 TDD 达不到要求时

虽然 TDD 是一种强大的方法,但它并不总是适合这项工作的工具。以下是应用 TDD 弊大于利的几种场景:

  • 不明确或不断变化的需求:当需求不明确或仍在不断变化时,预先编写测试可能会让人感觉像是在黑暗中拍摄。在这些情况下,我们需要探索问题空间,尝试不同的方法,并随着了解更多而迭代设计。在理解代码应该做什么之前尝试锁定测试会浪费时间并扼杀创造力。

  • 低域逻辑:如果代码库主要关注处理输入/输出(I/O)操作或简单任务,那么首先编写测试没有什么价值。例如,构建“Hello World”程序就是 TDD 过度杀伤力的明显例子。代码太简单、太稳定,几乎没有逻辑可供测试。如果代码与数据库、文件系统或 API 等外部系统大量交互,则预先编写单元测试可能会很不方便且效率较低。边界代码(例如 I/O)与实际域逻辑的比例较高意味着 TDD 的投资回报较低。

何时使用 TDD

现在让我们看看 TDD 何时大放异彩。根据我最近的经验,我发现 TDD 在以下场景中效果最好:

  • 明确的要求:在我提到的项目中,要求非常明确。我确切地知道每个函数对于每种类型的输入的预期输出应该是什么。这使我能够首先编写测试,并确信它们反映了所需的行为。由于清晰,测试指导了开发,确保我的实施是正确的并从一开始就满足业务需求。

  • 复杂领域逻辑:如果您正在开发一个具有大量复杂业务规则的系统,那么 TDD 就变得非常有价值。在这种情况下,每个测试都会验证逻辑的特定部分是否正确运行。我亲身经历过这一点:添加每个新功能后,我运行测试以查看缺少的内容并迭代代码,直到所有测试都通过。这让我相信每一个新的改变都不会破坏任何现有的行为。

为什么要使用 TDD

  • 行为驱动焦点:首先编写测试帮助我专注于行为,而不是实现。这是一个重要的区别,因为它可以防止测试与代码的内部工作过于紧密地耦合。相反,测试反映了代码应该做什么,使重构变得更容易,而不会不必要地破坏测试。

  • 长期维护:TDD最大的优势之一就是它带来的长期稳定性。当我稍后重新访问该项目以添加新功能或改进现有功能时,我现有的测试充当了安全网。我可以自信地做出改变,而不必担心回归,因为测试确保一切仍然按预期工作。

实例:使用 TDD 构建自定义 Deck 验证 DSL

在我最近的一个项目中,我应用 TDD 构建了自定义《炉石传说》套牌验证领域特定语言 (DSL)。目标是创建一个系统,允许用户以人类可读的格式定义复杂的套牌构建规则。首先,我设计了该语言的外观,以及它应该涵盖哪些场景。这种明确的需求加上系统的复杂逻辑,使其成为 TDD 的理想用例。

该项目有两个核心组件,它们从 TDD 方法中受益匪浅:

  • RuleValidator:该组件负责验证用户的输入,以确保其遵循 DSL 的语法和语义。它对输入进行标记,检查结构中的错误,并返回验证错误列表,并为用户提供清晰的消息。如果列表为空,则表示输入有效。 TDD 方法确保在实施过程中测试所有可能的验证场景,包括边缘情况。

    • 测试
    • 执行
  • RuleGenerator:输入经过验证后,RuleGenerator 会将其转换为定义套牌构建规则的 TypeScript 代码。它首先调用 RuleValidator 来确认输入是否正确。对于有效输入,它会根据属性、运算符、修饰符和值生成表示规则的函数。然后,DeckValidator 使用生成的代码来验证这副牌中的牌是否遵循定义的规则。

    • 测试
    • 执行

通过首先编写测试,我确保覆盖了为 DSL 设计的所有场景,这从头到尾指导了开发过程。这些测试充当清单,帮助我验证每个功能是否正确且完整地实现。这个过程还使开发更加顺利,我只是运行测试套件并完成所有失败的测试,而不是依靠内存来跟踪仍需要完成的工作。

当我重构代码时,TDD 的好处变得更加明显。例如,我将较大的功能分解为较小的、可重用的功能,从而改进了整体设计。此外,当我决定添加新的修饰符(> 和

结论

TDD 在正确的环境中使用时是一种有价值的方法。当您有明确的需求、高水平的领域逻辑并且需要一种可靠的方法来防止随着时间的推移出现回归时,它会表现出色。但是,它可能会在探索阶段或对于琐碎的、边界繁重的代码减慢您的速度。知道何时应用 TDD 以及何时推迟是充分利用 TDD 的关键。

版本声明 本文转载于:https://dev.to/mateuscechetto/when-does-tdd-make-sense-5h16?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 详解Javascript事件循环机制及规则
    详解Javascript事件循环机制及规则
    众所周知,javacript是一种线程语言,但是为什么我们可以使用异步操作?因为异步操作是由具有多线程和多进程功能的浏览器执行的。 JavaScript始终在整个线程上运行,并在异步代码被验证时将其移交给浏览器以进行执行,然后浏览器调用相应的线程或进程,包括HTTP请求,GUI,事件触发等,以处理...
    编程 发布于2025-04-29
  • 如何从PHP中的数组中提取随机元素?
    如何从PHP中的数组中提取随机元素?
    从阵列中的随机选择,可以轻松从数组中获取随机项目。考虑以下数组:; 从此数组中检索一个随机项目,利用array_rand( array_rand()函数从数组返回一个随机键。通过将$项目数组索引使用此键,我们可以从数组中访问一个随机元素。这种方法为选择随机项目提供了一种直接且可靠的方法。
    编程 发布于2025-04-29
  • 在H2数据库中使用JDBC插入和获取`java.time.LocalDate`对象的技巧
    在H2数据库中使用JDBC插入和获取`java.time.LocalDate`对象的技巧
    使用JDBC在H2数据库等SQL数据库中插入和检索java.time.LocalDate对象 问题: 如何使用JDBC在H2数据库引擎等SQL数据库中插入和检索java.time类型(例如LocalDate)? 解答: 方法一:兼容JDBC 4.2的驱动程序 对于符合JDBC 4.2规范或更高版本...
    编程 发布于2025-04-29
  • C++20 Consteval函数中模板参数能否依赖于函数参数?
    C++20 Consteval函数中模板参数能否依赖于函数参数?
    [ consteval函数和模板参数依赖于函数参数在C 17中,模板参数不能依赖一个函数参数,因为编译器仍然需要对非contexexpr futcoriations contim at contexpr function进行评估。 compile time。 C 20引入恒定函数,必须在编译时进行...
    编程 发布于2025-04-29
  • 如何在php中使用卷发发送原始帖子请求?
    如何在php中使用卷发发送原始帖子请求?
    如何使用php 创建请求来发送原始帖子请求,开始使用curl_init()开始初始化curl session。然后,配置以下选项: curlopt_url:请求 [要发送的原始数据指定内容类型,为原始的帖子请求指定身体的内容类型很重要。在这种情况下,它是文本/平原。要执行此操作,请使用包含以下标头...
    编程 发布于2025-04-29
  • 将图片浮动到底部右侧并环绕文字的技巧
    将图片浮动到底部右侧并环绕文字的技巧
    在Web设计中围绕在Web设计中,有时可以将图像浮动到页面右下角,从而使文本围绕它缠绕。这可以在有效地展示图像的同时创建一个吸引人的视觉效果。 css位置在右下角,使用css float and clear properties: img { 浮点:对; ...
    编程 发布于2025-04-29
  • 如何从PHP会话数组中选择性删除特定变量?
    如何从PHP会话数组中选择性删除特定变量?
    在php session中删除PHP会话阵列中的特定变量,可以存储各种变量以供以后使用。但是,您可能会遇到只需要删除特定变量的情况。以下是完成此任务的方法: 开始,您已经定义了如何将变量添加到会话中。让我们专注于您打算使用Unset删除变量的部分。不幸的是,使用Unsot($ _会话['n...
    编程 发布于2025-04-29
  • 如何使用“ JSON”软件包解析JSON阵列?
    如何使用“ JSON”软件包解析JSON阵列?
    parsing JSON与JSON软件包 QUALDALS:考虑以下go代码:字符串 } func main(){ datajson:=`[“ 1”,“ 2”,“ 3”]`` arr:= jsontype {} 摘要:= = json.unmarshal([] byte(...
    编程 发布于2025-04-29
  • Go语言垃圾回收如何处理切片内存?
    Go语言垃圾回收如何处理切片内存?
    Garbage Collection in Go Slices: A Detailed AnalysisIn Go, a slice is a dynamic array that references an underlying array.使用切片时,了解垃圾收集行为至关重要,以避免潜在的内存泄...
    编程 发布于2025-04-29
  • 如何使用Python有效地以相反顺序读取大型文件?
    如何使用Python有效地以相反顺序读取大型文件?
    在python 中,如果您使用一个大文件,并且需要从最后一行读取其内容,则在第一行到第一行,Python的内置功能可能不合适。这是解决此任务的有效解决方案:反向行读取器生成器 == ord('\ n'): 缓冲区=缓冲区[:-1] ...
    编程 发布于2025-04-29
  • C#中Int与Int32:选哪个整数类型?
    C#中Int与Int32:选哪个整数类型?
    C# 中 int 和 Int32 的使用 在 C# 中处理整数时,您可能会遇到两个术语:int 和 Int32。这两个术语代表相同的数据类型,都存储 32 位整数。然而,一个常见的问题是:应该使用 int 还是 Int32? 虽然 int 和 Int32 的功能完全相同,但某些考虑因素可以指导您的...
    编程 发布于2025-04-29
  • 哪种在JavaScript中声明多个变量的方法更可维护?
    哪种在JavaScript中声明多个变量的方法更可维护?
    在JavaScript中声明多个变量:探索两个方法在JavaScript中,开发人员经常遇到需要声明多个变量的需要。对此的两种常见方法是:在单独的行上声明每个变量: 当涉及性能时,这两种方法本质上都是等效的。但是,可维护性可能会有所不同。 第一个方法被认为更易于维护。每个声明都是其自己的语句,使其...
    编程 发布于2025-04-29
  • 对象拟合:IE和Edge中的封面失败,如何修复?
    对象拟合:IE和Edge中的封面失败,如何修复?
    To resolve this issue, we employ a clever CSS solution that solves the problem:position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%)...
    编程 发布于2025-04-29
  • 如何使用node-mysql在单个查询中执行多个SQL语句?
    如何使用node-mysql在单个查询中执行多个SQL语句?
    Multi-Statement Query Support in Node-MySQLIn Node.js, the question arises when executing multiple SQL statements in a single query using the node-mys...
    编程 发布于2025-04-29
  • MySQL中如何高效地根据两个条件INSERT或UPDATE行?
    MySQL中如何高效地根据两个条件INSERT或UPDATE行?
    在两个条件下插入或更新或更新 solution:的答案在于mysql的插入中...在重复键更新语法上。如果不存在匹配行或更新现有行,则此功能强大的功能可以通过插入新行来进行有效的数据操作。如果违反了唯一的密钥约束。实现所需的行为,该表必须具有唯一的键定义(在这种情况下为'名称'...
    编程 发布于2025-04-29

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3