问题 试图对TDD的好处充满信心


我刚买 单元测试的艺术 来自亚马逊我非常认真地了解TDD,所以请放心,这是一个真正的问题。

但我觉得我总是处于寻找放弃它的理由的边缘。

我将在这里扮演魔鬼的拥护者并试图击败TDD的所谓好处,希望有人可以证明我错了,并帮助我对其美德更有信心。我想我错过了一些东西,但我无法弄清楚是什么。

1. TDD减少错误

这个 经常被引用的博文 说单元测试是设计工具而不是用于捕获错误:

根据我的经验,单元测试不是   找到错误或检测回归的有效方法。

...

TDD是一种强大的设计方式   软件组件(“单位”)   交互式地使他们的行为   通过单元测试指定。   就这样!

说得通。边缘情况仍然总是在那里,而你只会找到表面上的错误 - 无论如何,只要你运行你的应用程序就会找到它们。在构建了大量软件之后,您仍需要进行适当的集成测试。

公平地说,减少错误并不是TDD应该提供的唯一帮助。

2. TDD作为设计范例

这可能是最重要的一个。 TDD是一种设计范例,可以帮助您(或强迫您)更多地创建代码 可组合

但可组合性是多重可实现的质量;例如,函数式编程风格使代码也可以组合。当然,完全以功能样式编写大型应用程序很困难,但是为了保持可组合性,您可以遵循某些折衷模式。

如果您从高度模块化的功能设计开始,然后根据需要仔细添加状态和IO到您的代码,您将最终得到TDD鼓励的相同模式。

例如,为了在数据库上执行业务逻辑,IO代码可以在一个函数中被隔离,该函数执行访问数据库并将其作为参数传递给负责业务逻辑的函数的“monadic”任务。这将是实现它的功能性方法。

当然,这有点笨重,所以相反,我们可以将数据库IO代码的子集抛出到类中,并将其提供给包含相关业务逻辑的对象。这是完全相同的事情,是功能性做事方式的改编,它被称为存储库模式。

我知道这可能会给我带来非常糟糕的鞭挞,但有时候,我不禁觉得TDD只是弥补了OOP可以鼓励的一些坏习惯 - 可以通过一点点避免功能风格的灵感。

3. TDD作为文件

TDD据说可作为文档,但它只作为同行的文档;消费者仍然需要文本文档。

当然,TDD方法可以作为示例代码的基础,但是测试通常包含一定程度的模拟,这些模拟不应该在示例代码中,并且通常是非常人为的,因此可以评估它们与预期结果的相等性。

一个好的单元测试将在其方法签名中描述正在验证的确切行为,并且测试将不再验证该行为。

所以,我会说,你的时间可能会更好地用于完善你的文档。哎呀,为什么不先彻底完成文档,并称之为文档驱动设计?

4.用于回归测试的TDD

在上面的帖子中提到,TDD对于检测回归并不太有用。当然,这是因为当您更改某些代码时,非显而易见的边缘情况总是会陷入困境。

关于该主题还可能需要注意的是,机会很好  您的代码将在很长一段时间内保持不变。因此,无论何时更改代码,保留旧代码并将其结果与新函数进行比较,根据需要编写单元测试是不是更有意义?


11328
2017-07-18 02:37


起源

“可以通过功能风格的一点点灵感来避免。”几乎同意...... TDD鼓励更接近Actor模型的编码风格,这是函数风格的反转(与功能编码的拉动相比,它是推模型) - kyoryu
有意思......我应该看一下Actor模型。这可能是我正在寻找的答案! - Rei Miyasaka
值得注意的是,Alan Kay被引用说他后悔使用“面向对象”一词,并希望他会使用“面向消息”。 TDD将您推向面向消息的风格(这是Actor模型的核心,或者是沟通顺序进程,或者......) - kyoryu


答案:


我相信TDD的好处在于你实际上编写了测试,因为当它们是你必须实现的目标时更有趣,(创建通过测试的代码),而不是之后你必须做的苦差事。

此外,它让您了解用户的想法。你必须考虑“那么用户需要我的方法做什么”或者其他什么,而不是“我希望我的方法已经实现了它应该做的”。通过这种方式,它也可以帮助减少错误。


3
2017-07-18 03:03



不确定我是否相信TDD对于此目的而言并不过分,但这似乎是TDD最明智的动机。 - Rei Miyasaka
那么我还可以为此做一个待办事项清单...... - Camilo Martin
是的,这就是事情,这就是为什么在将近一年后我仍然不相信。 - Rei Miyasaka


在设计方面,你没有看到TDD的一个主要优点是它驱动设计就足够了。你知道他们说工程师认为玻璃杯的大小应该是它应该的两倍。软件中的过度设计可能是一个大问题。我发现90%以上的时间TDD强制推出正确的抽象平衡,以支持以后的代码扩展。 TDD并不神奇,它背后的程序员也可以这样做,但它是工具包的重要组成部分。

我认为你的列表中存在过多的TDD。重构怎么样?我认为测试的一个主要优点是它可以锁定行为,因此当你重构时,你可以确信你没有改变任何东西,这反过来可以让你对重构充满信心。没有什么比设计出生的经验而不是白板(尽管高级白板设计仍然非常重要)。

另外,你写道:“那么,每当代码改变时,根据需要编写单元测试,保留旧代码并将其结果与新函数进行比较,是不是更有意义?”在没有单元测试的情况下编写的代码通常是不可测试的,特别是如果它与外部服务(如数据库,事务管理器,GUI工具包或Web服务)交互时。稍后添加它们就不会发生。

我认为Bob Martin说得最好,TDD就是编写Double Entry对Accounting的编程。它可以防止某类错误。它并不能防止所有问题,但确实如果你的程序想要添加两加二,它就不会减去它们。基本行为很重要,当它出错时,您可以花费大量时间来了解调试器。


5
2017-07-18 03:39



嗯...强迫你写“恰到好处”的代码实际上也是强制可组合性的影响。理解引用透明度和副作用之类的东西会迫使你的代码做得“足够”。在我的第二点中,我称之为“折射”,它使代码更具可组合性。根据定义,可组合软件易于折射。在大多数情况下,考虑到某些规则,我可以从函数式派生出OOP模式,这些模式产生的模块化代码往往看起来和感觉很像TDD代码。 - Rei Miyasaka
@Rei,如果你的论点是理论上可以在没有TDD的情况下获得类似TDD的代码,那么没有人与你争论(甚至是TDD的核心拥护者)。但是,这并不容易或容易。 - Yishai
关于重构,你制作软件的组合方式并不重要,如果你想重构代码的内部结构,你会害怕没有安全的网络。除非你异常善良或异常愚蠢。 - Yishai
实际上,在没有TDD的情况下编写出色的OO代码是可能的 - 但大多数情况下都没有,而且TDD是教授优秀OO代码的好方法。 - kyoryu


TDD不是一种方法论,它是一种心态。

TDD减少错误 :随着您的代码库开始增长,在每个签入到源代码管理上运行所有测试非常重要。当您在团队中有新成员时,这一点的好处就变得很明显了。

TDD作为设计范例: 测试是代码的第一批用户。最初,使用测试来驱动您的设计非常困难。但是,一旦你对它感到满意,你就会意识到测试代码实际上可以帮助你决定你的设计。这自然具有一些TDD经验。例如,使用TDD,您希望在界面中包装对服务的访问。这允许你模拟。但是,最重要的是它是正确的设计方式。

TDD作为文档 该文档用于代码文档,供代码开发人员使用。开发人员发现很容易阅读编写良好的单元测试,而不是页面和文档页面。


2
2017-07-18 05:45





在Javascript或任何必须在浏览器等多个古怪环境中运行的语言中,单元测试是一种很好的方式 Internet Effing Explorer搞砸了 要解决的问题。

至少它比锤击F5和使用更好 console.log() 甚至是警报。

但话说回来,我认为这应该主要用于中间件或复杂的RIA - 因为TDD用户界面几乎不可能有很少的业务逻辑。

如果你写下一个jQuery,单元测试将是你的朋友! :)

哦,让我们不要忘记js的伟大工具 JSTestDriver


1
2018-03-07 00:39



确实如此,当单元测试有用时,这是一个非常极端的情况:当平台本身发生变化时,您需要确保代码在所有代码中的行为始终如一。在那种情况下,你真的别无选择,只能用痛苦的细致来测试一切。总的来说,我理解单元测试的动机比我对TDD的激励更多。 TDD对我来说仍然非常不满意。 - Rei Miyasaka


我只想强调一下:
TDD测试不是单元测试

“单元测试的目的是单独测试一个代码单元。”
“与单元测试不同,TDD测试可能会一次测试多个代码单元。” TDD测试是更多的交互测试....

来自Stephen Walter非常好的博文 TDD测试不是单元测试 


1
2017-07-18 03:28





TDD还为您提供了自动化单元测试完全涵盖的代码。这只是因为在完成失败的单元测试通过之前,不会编写任何代码。


0
2017-07-18 03:16