在编写源代码之前还是之后进行TDD和测试?

4
我看过很多有关为何测试驱动开发(TDD)优秀以及如何减少开发时间等方面的文章。但是在搜索了许多论坛后,我仍未能得到TDD的具体优势。我并不是说测试是坏事,但我的观点是,如果我在编写源代码后编写单元测试,而不是像TDD建议的那样先编写测试用例,那么会有什么危害呢?而且这两个测试用例完成后都像回归测试一样。在尝试遵循TDD修改遗留代码时,我也遇到了许多问题。我猜现在大部分代码都是遗留代码,在没有预先存在的测试的情况下修改代码。此外,TDD是否仅限于单元测试,还是包括系统级别和集成测试。我只是无法想象我们如何在没有编写源代码的情况下进行集成测试。
10个回答

14

让我通过发问来回答:

如果你曾经要编写软件,你是否会从收集和撰写软件需求开始呢?

TDD的基本原则表明,你的测试用例就是你的需求。因此,通过首先编写测试,你就是在首先编写需求,只是以不同的方式实现而已。

这是最佳做法吗?这是主观的。但这就是为什么TDD要先编写测试的本质原因。


需要注意的是,需求经常会发生变化,因此为不正确的需求计划和编写详细测试用例所花费的时间是浪费的。一个好的折衷方案可能是稍微更注重广度优先,并延迟编写测试,直到您拥有一个松散工作的组件原型并且相当确定它需要做什么。 - Mukul M.
@AUser 很好的观点,但你涉及到了我没有关注的细节。在TDD中,通常会构建更高级别的特定需求的集成测试。这些是简单的测试,可以用作回归测试,以确保代码的更改不违反要求。当这些要求发生变化时,这些集成测试也会随之改变以适应变化的要求。但是,这很重要,集成测试只是您的测试套件的一部分。您仍应进行更复杂的灰盒测试--内部和/或子组件的黑盒测试。 - Randolpho
你能举个“基于需求的集成测试”的例子吗?我发现要想在没有实现活动的情况下编写测试,需求描述过于抽象。你是指编写组件的存根并测试它们如何相互通信吗? - Mukul M.

6
如果你在编写代码之后再编写测试(尤其是你已经编写过的代码),那么危险就在于你最终会编写一些你知道代码能够通过的测试,而不是编写确保正确行为的测试。这就像在开发软件之后再编写需求文档一样。
此外,测试驱动开发并不一定意味着你编写所有的测试,然后坐下来编写能够满足这些测试的代码。通常情况下,你会先编写测试,然后编写能够通过这些测试的代码,并不断迭代这个过程。

6

在尝试在遗留代码中实践TDD时,我也遇到了很多问题。

这也是为什么在编写测试代码之前编写被测试代码通常更好的原因之一。如果您先编写代码,然后再尝试进行测试,您可能会发现需要重新设计或完全重写类以使其更易于测试。如果您先编写测试代码,可以鼓励自己编写干净、可测试的接口来访问您的类。


5
我不会说TDD缩短了开发时间。甚至可能更长。但是TDD可以带来“干净的代码和可行的代码”。软件与单元测试同时增长,而不是一个接一个地增长,因此在编写时就进行了测试。这不仅给开发者带来信心,还让他知道自己所做的工作已经完成。此外,事后编写单元测试也很困难。作者 "Working effectively with legacy code"(非常好的资源)甚至说没有单元测试的代码确实是遗留代码。
TDD是一种开发技术,它并不意味着取代其他类型的测试。但是人们可以在要测试的代码不存在之前编写集成测试。这样可以询问自己如何测试将要生成的代码。

4

先编写测试确保可以对测试对象(System Under Test,简称SUT)进行单元测试。

如果您在编写测试之后再进行开发,您会发现许多情况下由于关于运行时环境的假设可能不成立,因此无法对SUT进行单元测试。


2

先写测试能够让你站在软件单元的“客户”角度。如果你尝试先写测试,你会更倾向于编写最容易使用的代码,而不是最容易编写的代码。

当我先写测试时,我也倾向于进入一种心态,即想象“嘿,如果我把这个、这个或那个传递给这个方法,我该怎么做”,因此在我花费太多时间编写最终没有用处的代码之前,有了更多的时间来思考设计、要求等方面的问题。

但确实,有时候你对如何编写某些东西一无所知,你只能去写它,然后再写一些测试,可能需要重构。没关系,只要最终获得一个合理的设计、足够干净的代码以及可以挽救回归的测试就行了…

当然,要编写集成测试,您必须有代码进行集成,所以很难完全按照TDD方式(但也许编写集成测试可以为单元测试提供模块接口的好主意,并作为单元测试的准备)。


2
我认为其中一个具体优点是你不会写比实际需要更多的代码。至少理论上是这样的。使用TDD,你从编写解决特定需求所需的所有测试开始。然后,你只编写足够使所有测试通过的代码。我无法确定这是否真正导致了更好的代码,但它可以节省你浪费时间在代码上,因为你没有意识到你已经完成了。
此外,先编写代码还是先编写测试的区别在于,当你先编写测试时,你实际上在考虑如何解决问题,而当你在代码之后编写测试时,你可能仅仅是在测试中表达代码。
“我也在试图遵循TDD来处理传统代码时遇到了很多问题。”
TDD和传统代码的问题是众所周知的。我阅读过的关于测试的每一本书或文章都特别指出,将TDD应用于现有代码要困难得多。
基本上,这不仅是你自己面临的问题,TDD支持者也已经认识到了。我认为其中一个解决方案是慢慢开始将TDD应用于你正在处理的代码部分,而不是直接跳进去并感到不知所措。
在我的工作场所,我们进行了大量的单元测试,但我们并没有严格遵循TDD。然而,我发现在修复错误时遵循TDD的规则可以帮助你更好地理解TDD。对于一个小问题,编写测试并看看TDD如何帮助找到问题并解决它通常比尝试将TDD应用于一个巨大的功能要容易得多。

1
但是在浏览了许多论坛之后,我仍然没有得到TDD的明确优势。
通过实践TDD,我学习了模块化设计原则。我逐渐增强了检测和消除重复代码、发现命名问题、将代码隔离成可重用的引擎以及保持依赖关系松散的技能。实践TDD让我有了一种逐步学习所有这些技能的方式。我认为这是TDD最大的具体优势。
您可以在这里阅读更多关于TDD为什么有效的信息:http://bit.ly/peWDS

1
TDD是一种确保所有功能都受到保护的方法。向已经存在的代码中添加测试意味着只有部分功能受到保护。

0
你如何确认代码可行?假设一家汽车公司希望确保你的代码100%安全。你是先完成代码再考虑测试安全呢?还是先通过测试定义代码结构并在将代码投入生产后保证代码按预期工作更好呢?
Bob Martin在他的博客中有一个关于何时不应使用TDD的好文章here,但通常情况下应该使用TDD。

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