如果 TDD 是针对单元的,那么测试怎么会意外地先通过(作为错误)?

If TDD is for units, how can a test accidentally pass first (as a mistake)?

我读过一份关于 TDD 的研究,其中一个常见问题(开发人员之间的调查)表明他们并没有真正让测试先失败。作者然后声明:

If a new test does not fail, programmers receive an indication that the production code was not working as they thought it was and a code revision might be necessary. Another problem that might occur is that programmers cannot be sure about what made the test pass; nothing ensures the new code was actually responsible for it. The test implementation might have been wrong since the beginning.

我想知道,TDD 测试怎么能先通过 (因为生产代码,就像他们提到的那样),如果它是在单元级别?我的意思是,如果所有的东西都被嘲笑(存根……),它应该总是被隔离,因此永远不能真正先通过。

假设您有两个 类 CalculatorFormatter.

Calculator根据输入计算出一些值,Formatter将该值转换为字符串显示。

您的FormatterTest中已经有一些测试:

  • test_value_is_formatted_as_number
  • test_empty_is_formatted_as_NA

现在您实现新功能 Show zero values as N/A

在 TDD 之后,您将向 Formatter test_zero_is_formatted_as_NA 添加一个测试,首先检查它,您预计它会失败:

def test_zero_is_formatted_as_NA(self):
    assert formatter.format(0) == 'N/A' 

但碰巧它通过了,原因是 Formatter 已经这样做了,但是 Calculator returns 精度有限的浮动零。

def format(value):
    if value == 0 or value is None:
        return 'N/A'
    return format_as_string(value)

所以测试通过了,但是如果你写另一个测试它会失败:

def test_very_small_number_is_treated_as_zero_and_formatted_as_NA(self):
    assert formatter.format(0.00000001) == 'N/A'

通常,当某些东西已经实现但系统的另一部分(使用这个已实现的部分)以某种方式限制它时,您描述的情况很容易发生,例如通过更强的先决条件。那么如果你对代码不太了解,你可能会遇到这样的惊喜。

考虑这个例子:

public string ShowScoreEvaluation(byte points)
{
    switch(points)
     case 3:
        return "You are good!";
     case 2:
        return "Not bad!";
     case 1:
        return "Quite bad";
     case 0:
        return "You suck!"

}

//caller code
if (Points>0)
  ShowScoreEvaluation(points)

在上面的代码中,调用代码不希望在 Points=0 时调用该方法。也许在该方法的实现过程中,程序员只是在 points=0.

的情况下放了一些东西(作为一个笑话或占位符)

现在想象一下,您加入该项目并收到一个新请求 "When player has 0 points, show an encouraging message blabla"。 您使用 Points=0 编写单元测试并期望长度>0 的字符串...但它并没有失败,尽管您希望如此。