TDD测试是否总是应该先失败?

11
作为对这个答案评论讨论的跟进,TDD测试是否总是要先失败?
考虑以下示例。如果我正在编写LinkedHashSet的实现,并且一个测试测试了在插入重复项后,原始项与插入之前相同的迭代顺序,我可能想要添加一个单独的测试,以确保重复项根本不在集合中。
第一个测试将首先被观察到失败,然后再进行实现。
问题在于很可能实现使第一个测试通过所使用的数据存储方式与第二个测试已经通过的副作用不同。
我认为看到测试失败的主要目的是确保测试是一个好的测试(很多次我写了一个我认为会失败但实际上没有因为测试写错了)。但是,如果您确信您编写的测试确实测试了某些内容,那么有必要确保您不会在以后破坏该行为,这难道不是有价值的吗?

我承认,最多只是一个动态语言的新手(因此是评论而不是答案),但我的直觉是——对于动态语言来说是的,而对于静态语言来说就取决于情况。在静态语言中编译失败的代码将在动态语言中运行,但会导致测试失败。为了让静态语言运行,您必须向受测试代码添加某种基线行为(即使只是空方法),这可能会导致正确编写的测试首先通过。 - Jeremy Frey
6个回答

10
当然有价值,因为这样就是一个有用的回归测试。在我看来,回归测试比测试新开发的代码更重要。
说它们必须先失败才行,这是把规则超出实用性了。

7
是的,TDD测试必须在变为绿色(工作)之前失败。否则您将不知道是否拥有有效的测试。

我不同意 -- 以一种静态语言为例,假设你有一个测试,如:some_value_should_always_be_false()。仅编写使测试通过的最少量的代码可能会使测试通过,除非你故意破坏测试并将被测试的值设置为true。 - Jeremy Frey
2
那么,你如何解决像OP中提出的情况? - Yishai
1
@Jeremy Frey - 你是正确的,不要破坏你的测试来使初始测试失败。目标不是语言本身,而是测试你的逻辑。在你提到的例子中,如果你可以假设值始终为false,那么这个测试似乎太琐碎了,无法增加价值。希望这能帮到你。 - Chris Ballance

4

我认为“先失败”的点在于避免欺骗自己,让测试通过。如果你有一组测试用例来检查同一个方法的不同参数,其中一个或多个最初可能会通过。考虑以下示例:

public String doFoo(int param) {
    //TODO implement me
    return null;
}

测试内容大致如下:
public void testDoFoo_matches() {
    assertEquals("Geoff Hurst", createBar().doFoo(1966));
}

public void testDoFoo_validNoMatch() {
    assertEquals("no match", createBar().doFoo(1));
}

public void testDoFoo_outOfRange() {
    assertEquals(null, createBar().doFoo(-1));
}
public void testDoFoo_tryAgain() {
    assertEquals("try again", createBar().doFoo(0));
}

其中一个测试将会通过,但是显然其他的测试不会通过,因此你需要正确地实现代码以使得所有测试都能通过。我认为这才是真正的要求。这个规则的精神在于确保你在开始编写代码之前就已经考虑好了期望的结果。


@John,你做了什么更改?在我看来,之前和之后的样子都一样。 - Rich Seller
@Rich:他添加了一些括号。你可以点击“编辑”旁边的链接,查看它被编辑的时间(就在他的名字上方),然后你就可以看到所有的差异和编辑注释了。 - Ahmad Mageed
@Ahmad,我这就做了。据我所知,带有删除线的文本和新文本是相同的(已经复制并粘贴第一个更改而没有格式)。createBar().doFoo(1966)createBar().doFoo(1966)。我错过了什么? - Rich Seller
你在所有测试行末尾都漏掉了')'。 - Nathan Koop
啊,谢谢。高亮的范围表明了更广泛的变化。 - Rich Seller

4

对于我来说,TDD更多是一种设计工具,而非事后的考虑。因此,没有其他的方式,测试将会失败,仅仅因为没有代码可以让它通过,只有当我创建代码之后测试才能通过。


1

实际上你想问的是如何测试测试以验证它是否有效并且测试了你的意图。

让它一开始失败是一个可以接受的选项,但请注意,即使在你计划它失败时它失败了,并且在你重构代码使其成功后它成功了,这仍然不意味着你的测试实际上测试了你想要的...当然,你可以编写一些其他行为不同的类来测试你的测试...但这实际上是测试你原始测试的测试-你怎么知道测试是有效的?:-)

所以让测试先失败是个好主意,但它仍然不是绝对可靠的。


0

在我看来,首先失败的重要性在于确保您创建的测试没有缺陷。例如,您可能会忘记在测试中使用Assert,而您可能永远不会知道。

当您进行边界测试时,类似的情况会发生,您已经构建了覆盖它的代码,但建议进行测试。

我认为,测试不失败并不是一个大问题,但您必须确保它确实测试了应该测试的内容(也许需要调试)。


另一种确保测试的准确性的技术是在被测试的代码中暂时引入一个错误,然后观察测试是否能够捕捉到它。这被称为“破坏者方法”。 - Pixelstix

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接