unit-testing - 试图获得对 TDD 好处的信心

标签 unit-testing testing functional-programming tdd

我刚买了The Art of Unit Testing来自亚马逊。我对理解 TDD 非常认真,所以请放心,这是一个真正的问题。
但我觉得我一直处于寻找放弃它的理由的边缘。
我将在这里扮演魔鬼的拥护者,并试图否定 TDD 所谓的好处,希望有人能证明我错了,并帮助我对它的优点更有信心。我想我错过了一些东西,但我不知道是什么。
1. TDD 减少bug
这个often-cited blog post说单元测试是设计工具,而不是用来捕捉错误:

In my experience, unit tests are not an effective way to find bugs or detect regressions.

...

TDD is a robust way of designing software components (“units”) interactively so that their behaviour is specified through unit tests. That’s all!


说得通。边缘情况仍然会一直存在,您只会发现表面的错误——不管怎样,只要您运行应用程序就会发现这些错误。在您构建好大部分软件后,您仍然需要进行适当的集成测试。
很公平,减少错误并不是 TDD 应该帮助的唯一事情。
2. TDD 作为设计范式
这可能是最大的一个。 TDD 是一种设计范式,可帮助(或强制)使您的代码更加 composable .
但是可组合性是一种可多次实现的品质。例如,函数式编程风格也使代码非常可组合。当然,完全以函数式风格编写大型应用程序是很困难的,但是您可以遵循某些折衷模式来保持可组合性。
如果您从高度模块化的功能设计开始,然后根据需要小心地将状态和 IO 添加到代码中,那么您最终会得到 TDD 所鼓励的相同模式。
例如,为了在数据库上执行业务逻辑,可以将 IO 代码隔离在一个函数中,该函数执行访问数据库的“单子(monad)”任务,并将其作为参数传递给负责业务逻辑的函数。这将是做到这一点的实用方法。
当然,这有点笨拙,因此,我们可以将数据库 IO 代码的子集放入一个类中,并将其提供给包含相关业务逻辑的对象。这是完全相同的东西,是对功能性做事方式的改编,它被称为存储库模式。
我知道这可能会给我带来非常糟糕的鞭刑,但很多时候,我不禁觉得 TDD 只是弥补了 OOP 可以鼓励的一些坏习惯——那些可以通过一点点来避免的坏习惯从功能风格中汲取灵感。
3. TDD 作为文档
TDD 被称为文档,但它仅作为对等点的文档;消费者仍然需要文本文档。
当然,TDD 方法可以作为示例代码的基础,但是测试通常包含一定程度的模拟,这些模拟不应该出现在示例代码中,并且通常非常人为,以便可以根据预期结果评估它们是否相等.
一个好的单元测试将在其方法签名中描述正在验证的确切行为,并且测试将验证的行为不多也不少。
所以,我想说,你最好把时间花在润色文档上。哎呀,为什么不先彻底完成文档,并称之为文档驱动设计?
4. 回归测试的TDD
在上面的那篇文章中提到,TDD 对于检测回归并不太有用。当然,这是因为不明显的边缘情况是在您更改某些代码时总是搞砸的情况。
关于该主题还可能需要注意的是,您的大部分代码很有可能在很长一段时间内保持不变。那么,根据需要编写单元测试是否更有意义,每当代码更改时,保留旧代码并将其结果与新函数的结果进行比较?

最佳答案

在设计方面,您没有看到的 TDD 的一个主要好处是它使设计变得足够。你知道他们怎么说工程师看到的玻璃是它应该有的两倍大。软件中的过度设计可能是一个大问题。我发现 90% 以上的时间 TDD 迫使抽象的正确平衡来支持以后的代码扩展。 TDD 并不神奇,它也需要它背后的程序员来做到这一点,但它是工具包的重要组成部分。

我认为您的列表中有太多孤立的 TDD。重构呢?我认为测试的主要好处之一是它锁定了行为,因此当你重构时你可以确信你没有改变任何东西,这反过来又可以让你对重构充满信心。没有什么比白板更能从经验中诞生的设计(尽管高级白板设计仍然非常重要)。

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

我认为 Bob Martin 说得最好,TDD 之于编程就像 Double Entry 之于会计。它可以防止某一类错误。它并不能避免所有问题,但可以确保如果您的程序打算将 2 加 2 相加,它不会改为减去它们。基本行为很重要,当它出错时,您可以花费大量时间来了解您的调试器。

关于unit-testing - 试图获得对 TDD 好处的信心,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3274112/

相关文章:

grails - 如何在 Grails 测试中从数据库中删除对象

javascript - 我试图在纯函数中将值移动到父节点

Javascript 与 ES6 的嵌套 for 循环

c++ - 如何使用 boost::test 设置 C++ turtle 模拟库?

java - Java 测试的标准是什么?

unit-testing - F# 测试结果是否为 Some(any)

java - Mockito 在测试中没有模拟来自 Controller 的功能

java - 如何使用 Stream API 随机播放流?

c++ - HippoMocks - 在 "ExpectCallFunc"之后使用 "NeverCallFunc"来实现相同的功能会导致意外的 "HippoMocks::ExpectationException"

unit-testing - 在 Visual Studio 中使用 Catch2 进行单元测试的最佳实践