在 TDD 中,当要测试的函数未定义时,如何先编写测试?

In TDD, how do you write tests first when the functions to test are undefined?

如果你什么都没有,你就不能写测试,因为没有什么可测试的。这对我来说似乎很明显,但 TDD 的支持者似乎从未解决过。

为了编写测试,您必须首先决定要测试的方法或函数是什么样的。您必须知道要传递给它的参数以及您期望返回的参数。这是第一位的,而不是测试。

测试永远不能放在第一位。首先是设计,它指定了将要存在的 类 和方法。

正好相反

如果您编写的测试调用不存在的函数,您的测试套件将失败,并且您会收到一个错误,迫使您定义该函数,就像编写任何其他测试会强制您编写实现一样。

您的测试不需要 运行 是好的测试。但是这种测试并不意味着要留在你的测试套件中。它们有时被称为“阶梯测试”:您需要编写它们才能开始,但它们只是工具性的。

通常发生的情况是,一旦此测试通过,您就会通过更具体的方式使其失败。从技术上讲,您最终得到的测试与事后编写的测试相同,并且编写它不需要更多时间,但在此过程中,您能够 运行 测试套件一次或多次,所以你在无效状态上花费的时间更少,可以这么说。

我想补充一点,你的问题没有什么不对的,但是你的结论并没有遵循这个前提:确实首先是规范,但是没有什么不符合形式化这个规范的代码编写前的测试。规范和测试迫使您编写代码。 TDD 是一种形式化规范的渐进方式,可确保规范始终排在第一位。

To write a test, you have to first decide what the method or function looks like that you're going to test. You have to know what parameters to pass to it and what you expect to get back. THAT is what comes first, NOT the test. Tests can NEVER come first. The thing that comes first is the design which specifies what classes and methods are going to exist.

不太正确(也不是完全错误 - 它很复杂[tm])

如果您查看示例测试驱动开发中的第一个示例,您会发现 Beck 并没有以 classes 和方法开头。他甚至没有开始测试。

他创建的第一件事是一个“待办事项”列表,待办事项列表中的每个条目都是一种行为的表示(我的术语,不是他的术语)。所以我们看到像

这样的东西
 + 10 CHF =  if rate is 2:1

如今,您更有可能看到这个想法被表达为霍尔三元组(Given/When/Then、Arrange/Act/Assert 等)。但是我们这里提醒程序员我们需要一个自动检查来测量将两种不同货币加在一起的结果,并确认结果符合某些规范。

在他的练习中,他的待办事项列表包括一个“更简单”的测试,这是他首先尝试的测试

 * 2 = 

同一个待办事项列表还包括一些其他关于设计的关注点,没有以测试形式表达。此外,列表会随着他解决问题而增加。

从这个意义上说,考试绝对是第一位的。我们用一种供人类使用的语言编写测试。稍后将测试翻译成机器可以理解的语言。


在第二步中,我们向机器描述测试,事情变得更加混乱。绝对是这样的,当我们设计测试时,我们也在设计允许测试测量生产代码功能的通信协议。因此,有一定数量的通信设计与“测试”设计并行进行。

但即使在这里,测试也没有指定将要存在的 all 个 classes,它只是指定执行测量所需的内容。我们描述了一个立面,但我们没有具体说明立面之外是什么。

当我们设计更多的系统时,可能会发生我们指定的外观仅供测试使用,作为与生产代码的不同底层设计进行通信的一种方式。

(注意:我在这里说 classes 是为了与问题和早期文献保持一致,主要取自 Smalltalk 或 Java 中的示例。请随意将“功能”替换为“classes" 如果这让你更舒服。)

现在,最常见的情况是门面是生产代码;我们通常不会在设计中添加元素,除非我们对它们有非推测的动机。


“单元测试”对这些想法造成了一些压力 - 如果不首先设计单元边界,您怎么可能编写单元测试?

真正的答案是不幸的——Kent Beck 没有编写单元测试。他编写了“程序员测试”(后来重新定义的术语)并它们为单元测试。

使用 1990 年代的测试语言(所有这些混乱开始的时候),更合适的术语可能是“复合测试”。

您还有“伦敦学派”,它试图弄清楚如何对特定设计风格进行 TDD;为这种风格编写测试需要更复杂的“预先”测试外观(角色和接口以及稳定的替代实现等)。


设置也值得牢记。

(免责声明:这不是我亲眼目睹的 - 认为“基于真实故事”而不是“事实”)

TDD(及其在 XP 中的“先测试”编程的父思想)正在反对那种由您决定 class 层次结构和关系应该是什么并记录它们的“前期设计” ,在你真正坐下来写代码之前

核心论点是设计过程需要更短的反馈循环;在我们获得大量证据表明它会成功之前,我们不会深入致力于特定的设计。


综上所述,是的,TDD 作为一种技术,在已经精通软件设计的人手中效果好得多,这绝对是事实。参见 Look, Ma, no hands! 时代的 Michael Feathers。

没有魔法。

的确,为了编写测试,测试编写者必须对测试代码如何与被测系统交互形成一些概念。从这个意义上说,概念设计'comes first'.

但是,测试驱动开发 (TDD) 很有价值,因为它不仅仅是(仅)一种质量保证方法。它首先是一个 快速反馈回路

虽然您心中可能有一个初始设计,但一旦您开始编写测试,您可能会发现这个设计不起作用(或使用起来很笨拙)。这种情况经常发生,应该会促使您立即调整路线。

red-green-refactor cycle 提出了一个考虑 TDD 的模型。每个这样的周期可能是一两分钟。

因此,您可以从脑海中的初始设计开始,然后每隔一分钟对其进行调整(或完全重新考虑)。

never seems to be addressed by proponents of TDD

我不同意。很多 TDD 介绍都讨论了这一点。两本讨论这个(以及更多)的好书是 Kent Beck 的 示例测试驱动开发 和 Nat Pryce 和 Steve Freeman 的 Growing Object-Oriented Code by Tests.