什么是测试驱动开发(TDD)?是否需要初始设计?

9

我对测试驱动开发(TDD)非常陌生,还没有开始使用它。 但我知道我们必须先编写测试,然后编写实际代码以通过测试并重构它,直到设计良好。

我对TDD的担忧是它在系统开发生命周期(SDLC)中的位置。 假设我有一个制作订单处理系统的需求。 现在,如果没有任何模型或设计,我如何开始编写测试? 难道我们不需要定义实体及其属性才能继续吗? 如果不是,是否可能开发一个大型系统而没有任何设计?


3
请参见Bob大叔在相关问题中的回答- https://dev59.com/THI-5IYBdhLWcg3wMFIf#1985582 - RichardOD
9个回答

9

有两种TDD,一种是ATDD或验收测试驱动开发,另一种是由单元测试驱动的普通TDD。

我猜TDD和设计之间的关系受到了“敏捷”概念的影响,即源代码是软件产品的设计。许多人通过将TDD翻译为测试驱动设计而不是开发来强化这一点。这是有道理的,因为TDD应该被视为与驱动设计相关的内容,而不是测试。拥有验收和单元测试只是一个很好的附带效果。

如果不了解更多信息,我不能太多地谈论它在您的SDLC中的适用性,但一个不错的工作流程是:

对于每个用户故事:

  1. 使用类似FitNesseCucumber的工具编写验收测试,这将指定给定输入的期望输出,从用户理解的角度自动化规范,甚至在理想情况下可以替代规范文档。

  2. 现在您可能对所需软件设计的类/行为等有一个模糊的想法。

  3. 针对每个行为:

  4. 编写一个失败的测试,展示了您希望使用该类的调用代码

  5. 实现使测试通过的行为

  6. 重构测试和实际代码以反映良好的设计。

  7. 进入下一个行为。

  8. 进入下一个用户故事。

当然,整个时间您都会考虑系统逐渐演变的高级设计。理想情况下,TDD会在较低层面上产生灵活的设计,允许适当的高级设计随着进展而演变,而不是试图在开始时猜测它。

1
@Kurt,这是我见过的关于TDD最好的答案。恭喜你。(+1) - Arthur Ronald
那么,所有的模式和设计原则怎么办呢?在编写测试和实际代码并行时,我们如何确定应该在哪里应用什么模式和设计原则。 - Nirajan Singh
如果每个开发人员都按照自己的风格开发,那么我们的软件产品会处于什么位置呢?大多数情况下,一个团队开发一部分,另一个团队开发另一部分。这将在组件之间引发兼容性问题。 - Nirajan Singh
@Nirajan 识别模式和设计原则来自于学习和经验。TDD实际上有助于自然地避免许多反模式。这是一个非常详细的话题,但Robert C. Martin等人的书籍是该主题知识的重要来源。跨不同团队的合作也是一个重要的话题。这个话题太大了,无法在本评论中解决,但是按功能垂直切片工作,而不是分层,可以帮助解决问题。沟通是避免问题的关键,而TDD产生的测试作为副作用有助于更快地识别问题。 - Kurt
感谢您的建议。实际上,我为演示应用程序编写了一些测试。如何决定我们需要重构代码的程度。由于我是TDD的新手,是否在没有足够知识的情况下在我的项目中使用它是一个好习惯呢? - Nirajan Singh

6
它应该被称为“测试驱动设计”,因为这就是它的本质。
将设计分为特定的项目阶段没有实际意义。设计一直都在发生。从与利益相关者的初步讨论开始,到用户故事的创建、估算,然后当然还包括您的TDD会话过程中。
如果您想使用UML或其他形式来规范化设计,那很好,只需记住代码即是设计。其他所有东西只是近似值。 并且请记住You Aren't Gonna Need It (YAGNI)适用于所有内容,包括设计文档。

1
非常好的答案。实际上,在精神上与这个不错的答案非常相似。 - Henke

4
写测试先行让你先考虑问题领域,并充当一种规范说明。然后在第二步中,您转向 解决方案领域 并实现功能。
TDD 迭代工作效果很好:
  1. 定义您的初始问题领域(可以是小型、演进的原型)
  2. 实施它
  3. 扩展问题领域(添加功能,增加原型)
  4. 重构并实现它
  5. 重复步骤 3。
当然,您需要事先有一个模糊的架构愿景(技术、层、非功能要求等)。但是,为您的应用程序带来增加值的功能可以很好地通过 TDD 引入。
请参见相关问题:TDD:适合初学者吗?

2
使用TDD,你不必过多关注设计。想法是,在开始有用的设计之前,你必须首先了解你需要什么。测试确保在需要决定你的设计时,你可以轻松可靠地更改应用程序。
没有TDD,会发生这种情况:你制定一个设计(可能在某些领域过于复杂,并且由于你不知道某些重要事实而忘记考虑)。然后你开始实施设计。随着时间的推移,你意识到设计的所有缺点,所以你改变它。但是改变设计并不会改变你的程序。现在,你尝试更改代码以适应新的设计。由于代码不是为易于更改而编写的,这最终会失败,使你拥有两个设计(一个损坏的和另一个处于未知状态)和不适合任何一个的代码。
要开始使用TDD,请将您的要求转化为测试。为此,请问“我如何知道这个要求已经实现?”当你能够回答这个问题时,编写实现这个问题答案的测试。这给你的(待编写)代码必须遵循的API。这是一个非常简单的设计,但是a)始终有效,b)是灵活的(因为你无法测试不灵活的代码)。
同时,从测试开始会让你成为自己的客户。由于你努力使测试尽可能简单,所以你会创建一个简单的API,使测试起作用。
随着时间的推移,你将学习足够的有关问题域的知识,以便能够进行真正的设计。由于你有大量的测试,因此可以更改代码以适应设计,而不会在途中终止任何内容。
这就是理论 :-)实际上,你会遇到一些问题,但它运行得非常好。或者说,它比我迄今遇到的任何其他东西都要好。

但是在提出需求时,客户总是要求对实现进行估计。在没有设计的情况下是否可能进行估计呢?我们知道编写测试和实际代码是并行任务,那么什么时候以及如何进行估计呢? - Nirajan Singh
你可以从测试开始谈起:客户必须帮助你详细说明测试内容(需要测试什么,预期结果等)。这将让你了解每个功能需要多少测试,因此也能了解一个功能有多“大”或复杂。 - Aaron Digulla

1
当然,首先你需要一个坚实的功能分析,包括一个领域模型。如果不知道要创建什么东西,就不可能编写单元测试。

1
只有你不能先进行“坚实的功能分析”,因为你需要知道最终需要什么,也就是预测未来 -> 这是不可行的。如果有人要求进行“坚实的功能分析”,他们会得到“尽可能好的目前所知道的”,然后他们会想知道为什么设计中有这些麻烦的缺陷。 - Aaron Digulla

1

我使用测试驱动的开发方式来编程,从经验可以说它有助于创建更健壮、专注和简单的代码。我的TDD步骤大致如下:

  1. 使用一个单元测试框架(我自己编写)编写代码,以便在测试中确保返回值等正确。这样可以确保只编写实际需要使用的代码。我还添加了一些额外的测试来检查边缘情况。
  2. 编译 - 你会得到编译器错误!!!
  3. 对于每个错误,添加声明,直到没有编译器错误。这样可以确保您的代码具有最小的声明。
  4. 链接 - 您将获得链接器错误!!!
  5. 编写足够的实现代码来消除链接器错误。
  6. 运行 - 单元测试将失败。编写足够的代码使测试成功。

你已经完成了。你编写了实现功能所需的最少代码,并且由于测试,你知道它是健壮的。你还将能够检测到未来是否出现问题。如果发现任何错误,请添加单元测试以测试该错误(例如,您可能没有考虑到边缘情况)。而且你知道,如果你为你的代码添加更多功能,你不会使它与使用你的功能的现有代码不兼容。

我喜欢这种方法。让我感觉温暖和舒适。


0

TDD意味着有一些现有的设计(外部接口)可以开始。您必须心中有某种设计,才能开始编写测试。有些人会说TDD本身需要较少详细的设计,因为编写测试的行为为设计过程提供了反馈,但这些概念通常是正交的。


0

你需要一些形式的规范,而不是一种设计形式——设计关注的是你如何实现某个东西,规范则关注你将要实现什么。

我见过的大多数与TDD(和其他敏捷流程)一起使用的规范形式是user stories——这是一种非正式的“用例”,通常用有些固定模式的英语句子来表达,比如“作为一个,我可以……”(用户故事的形式在具体的风格/流程中会更或少严格)。

例如,“作为客户,我可以开始一个新订单”,“作为客户,我可以向我的现有订单添加条目”等等,如果您的“订单输入”系统是这样的话,那么这可能是典型的(当然,如果该系统不是用户“自助”使用,而是旨在由销售代表代表用户输入订单,则用户故事将非常不同 - 没有知道所指的订单输入系统是什么类型,就无法明智地继续进行,这就是为什么我说您确实需要一些关于系统将要做什么的规范,尽管通常还没有完整的关于如何做到这一点的想法)。

0

让我分享我的观点:

如果你想要构建一个应用程序,在这个过程中你需要测试它,例如通过代码检查来检查你创建的变量的值,或者快速放置一个按钮,当你点击它时会执行一部分代码并弹出一个对话框来显示操作的结果等。另一方面,TDD改变了你的思维方式。

通常情况下,你只依赖于开发环境(如Visual Studio)来在编码和编译过程中检测错误,并且在你的头脑中,你知道需求并且只是通过按钮和弹出窗口或代码检查来进行编码和测试。这是一种语法调试驱动的开发方式。但是当你使用TDD时,它是一种“语义调试驱动的开发方式”,因为你首先使用测试(它们是白板的更动态和可重复版本)来写下你的应用程序的思路/目标,这些测试测试你的应用程序的逻辑(或“语义”),并且即使你的应用程序通过了语法错误(编译),只要有语义错误,测试就会失败。

在实践中,您可能不知道或没有构建应用程序所需的所有信息。由于TDD强制您首先编写测试,因此您被迫在开发的早期阶段就询问有关应用程序功能的更多问题,而不是一开始就构建很多内容,只发现您编写的大量内容并非所需(或者至少目前不需要)。尽管最初可能不会感觉如此,但使用TDD确实可以避免浪费宝贵的时间。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接