如何使用 TDD 指定循环?

How do you specify a loop with TDD?

我经常发现我不确定如何测试需要循环的代码。以下面的例子:

methodUnderTest(inputQueue) {
    while (inputQueue.count > 0) {
        process(inputQueue.dequeue());
    }
}

第一个测试可能如下所示:

processesInput1() {
   // Arrange
   inputQueue = [a];

   // Act
   methodUnderTest(inputQueue);

   // Assert
   Assert.wasProcessed(a);
}

合乎逻辑的下一个测试是:

processesInput2() {
   // Arrange
   inputQueue = [a, b];

   // Act
   methodUnderTest(inputQueue);

   // Assert
   Assert.wasProcessed(a);
   Assert.wasProcessed(b);
}

但当我们到达时:

processesInput3() {
   // Arrange
   inputQueue = [a, b, c];

   // Act
   methodUnderTest(inputQueue);

   // Assert
   Assert.wasProcessed(a);
   Assert.wasProcessed(b);
   Assert.wasProcessed(c);
}

这一切开始让人觉得有点多余。我曾经听说过关于 TDD 的一个很好的建议是将测试视为规范。测试在什么时候指定将处理所有 N 项的输入?如何在测试中最好地描述这一点?

一般来说,您想测试一条快乐的路径和边界条件。

从数学上讲,您想涵盖所有等价性 类。它意味着代表一组有意义的用例的输入。例如空队列、非空队列和满队列。

在您的案例中,测试 0 项和 2 项就足够了。

我同意您的评价 - 3 个项目感觉像是一个多余的测试(2 个项目已经足够 N 个项目的情况)。我还认为,一旦您对 2 个项目进行了测试,那么对 1 个项目的测试也是多余的,但是由于严格使用 TDD,当然最终会出现在那里。

从规范的角度来看,0 项测试和 N 项测试感觉就足够了,因此如果您想保持测试集最小,我建议删除 1 项测试。

就像其他答案所说的那样,测试零个和两个参数可能就足够了。但是,您可能想尝试使用 1000 个或更多。然后呢?

At what point does the test specify that an input of N items will all be processed? How best to portray this in a test?

现在这取决于模拟框架是否支持这样的事情,但是在 gmock 中你可以使用 .WillRepeatedly(action),在 action 中你可以进行额外的处理(比如在你的特定示例中检查数组值.

如果您使用 gmock,您还可以使用 .Times(cardinality).

检查模拟方法的调用次数

我建议你重新检查你原来的问题。 TDD 鼓励您通过指定应该发生什么来推动实施,一次一小步。我建议 'loop' 这个词在这个(或几乎任何其他)规范中是不必要的。相反,我希望看到这样的规范 "All inputs should be processed." 现在,队列中的输入数量(如果是这样的话)是任意的——要求是它们都应该被处理。

选择是否测试不同数量的输入取决于您对风险(发生问题)和收益(编写额外测试)的专业判断。作为 Kent Beck said:

“I get paid for code that works, not for tests, so my philosophy is to test as little as possible to reach a given level of confidence ... I suspect this level of confidence is high compared to industry standards”