【问题标题】:Should a TDD test always fail first?TDD 测试是否应该总是首先失败?
【发布时间】:2009-07-03 15:34:22
【问题描述】:

作为this answer 的 cmets 中讨论的后续,TDD 测试是否应该总是首先失败?

考虑以下示例。如果我正在编写LinkedHashSet 的实现,并且一个测试测试在插入副本后,原始的迭代顺序与插入之前的迭代顺序相同,我可能想添加一个单独的测试,即副本不在集合中全部。

第一个测试会先观察失败,然后再执行。

问题在于,使第一个测试通过的实现很可能使用不同的集合实现来存储数据,因此作为副作用,第二个测试已经通过。

我认为看到测试失败的主要目的是确保测试是一个好的测试(很多时候我写了一个我认为会失败但没有因为测试写错的测试)。但是,如果您确信自己编写的测试确实测试了某些东西,那么确保以后不会破坏这种行为不是很有价值吗?

【问题讨论】:

  • 我承认,充其量是一个动态语言新手(因此评论而不是答案),但我的直觉是——对于动态语言是的,它取决于静态语言。在静态语言中编译失败的代码将在动态语言中运行,但会导致测试失败。为了让静态语言运行,您必须在被测代码中添加某种基线行为(即使只是空方法),这可能会导致正确编写的测试首先通过。

标签: language-agnostic tdd


【解决方案1】:

当然它很有价值,因为它是一个有用的regression test。在我看来,回归测试比测试新开发的代码更重要。

说他们必须总是首先失败是在实践超出实际的规则。

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 我不同意——采用静态语言,您可以在其中进行一些测试,例如 some_value_should_always_be_false()。编写足够的代码来编译测试也可能会让测试通过,除非你故意破坏测试并将被测值设置为 true。
    • 那么您如何处理像 OP 中呈现的场景?
    • @Jeremy Frey - 您是正确的,不要破坏您的测试以使初始测试失败。目标不是语言本身,而是测试你的逻辑。在您提到的示例中,如果您可以假设该值始终为假,那么测试似乎太微不足道而无法增加价值。我希望这会有所帮助。
    【解决方案3】:

    对我来说,TDD 更像是一种设计工具,而不是事后的想法。所以没有其他办法,测试会失败,只是因为还没有代码让它通过,只有在我创建它之后,测试才能通过。

    【讨论】:

      【解决方案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));
      }
      

      其中一个测试会通过,但显然其他测试不会,因此您必须正确实现代码才能通过测试集。我认为这才是真正的要求。该规则的精神是确保您在开始 hack 之前已经考虑了预期的结果。

      【讨论】:

      • @John 你做了什么改变?之前和之后在我看来是一样的
      • @Rich:他加了一些括号。您可以单击“已编辑”旁边的链接,显示它是多久以前被编辑的(就在他的名字上方),您可以看到所有差异和编辑说明。
      • @Ahmad,这就是我所做的,据我所知,带有删除线的文本和新文本是相同的(复制并粘贴下面的第一个更改,没有格式化) createBar().doFoo (1966)createBar().doFoo(1966)。我错过了什么
      • 你错过了所有测试行的尾随')'
      • 啊,谢谢,高亮显示的范围更广
      【解决方案5】:

      您实际上要问的是如何测试测试以验证它是有效的并测试您想要的。

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

      所以首先让测试失败是个好主意,但它仍然不是万无一失的。

      【讨论】:

        【解决方案6】:

        恕我直言,首先失败的重要性在于确保您创建的测试没有缺陷。例如,您可能会忘记测试中的 Assert,而您可能永远不会知道这一点。

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

        我认为如果你的测试不失败并不是什么大问题,但你必须确保它确实在测试它应该做的事情(也许是调试)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-03-27
          • 1970-01-01
          • 2017-06-11
          • 1970-01-01
          • 1970-01-01
          • 2017-08-26
          • 2015-12-13
          相关资源
          最近更新 更多