【问题标题】:Should I write a Test that already passes?我应该写一个已经通过的测试吗?
【发布时间】:2014-04-12 18:30:55
【问题描述】:

我说的是鲍勃叔叔的TDD规则:

  1. 您不得编写任何生产代码,除非是为了通过失败的单元测试。
  2. 不允许您编写任何足以导致失败的单元测试;编译失败就是失败。
  3. 您编写的生产代码不得超过足以通过一个失败的单元测试的数量。

我的问题是,如果您希望创建一个可以生成多个结果的功能,并且在第一次迭代中您实现的代码可以满足所有场景,会发生什么?
我曾经写过这样的代码,因为这是我首先想到的唯一解决方案。

我会说我没有违反这 3 条规则中的任何一条。

  1. 我编写了一个测试失败条件最少的测试。
  2. 然后我用足够的代码实现了该功能以通过测试(这是我想出的唯一解决方案;所以我会说我可能编写的代码最少)。
  3. 然后我写了下一个测试发现它已经通过了。

现在规则呢?
即使该功能是 soooper 重要的功能,我是否也不允许编写此测试?还是我应该回滚并重新开始?

我还要提一下,这个方法不能根据输入的结果或数据进行重构。现在我能想到的示例情况有点愚蠢,但请多多包涵。看看这个例子:
我想创建一个添加数字的方法,这几乎是我能做的。白一个失败的测试:

public function it_can_add_numbers()
{
    $this->add(2, 3)->shouldReturn(5);
}

然后让它通过:

public function add($numberOne, $numberTwo)
{
    return $numberOne + $numberTwo;
}

现在有人可能会争辩说我应该在第一次迭代中返回 5,因为这足以通过测试并引入回归,但这不是一个实际问题,所以请耐心等待,假设这是唯一的可以想到的解决方案。
现在我的公司希望我确保他们能够将 12 和 13 相加,因为这些是我们会使用很多时间的内部神奇数字。我继续编写另一个测试,因为这是我应该验证功能的方式。

public function it_can_add_twelve_and_thirteen()
{
    $this->add(12, 13)->shouldReturn(25);
}

原来测试已经通过了。
在这一点上,我可以选择不写测试,但是如果以后有人对实际代码进行更改并完成它会怎样

public function add($numberOne, $numberTwo)
{
    return 5;
}

测试仍会通过,但该功能不存在。

那么,当您在进行改进之前无法立即考虑在第一次迭代中引入可能存在的缺陷时,该怎么办?我应该把它留在这里,等待有人过来把它搞砸吗?我应该把这个案例留给回归测试吗?

【问题讨论】:

  • 这里的错误是假设你应该始终严格和盲目地遵循这三个规则;)真正的规则是“当你认为它有价值时写一个测试”。
  • 我不止一次看到项目惨遭失败,只是因为一些经理盲目地采用了 tdd。很多时候,单元测试和测试覆盖率都被错误地用于覆盖没人愿意解决的实际问题
  • @OliCharlesworth 谢谢。这正是我想听到的,我个人也相信这一点,但我仍然看到很多人提倡规则。一些人认为,严格遵循它们最终会更好地塑造代码库。
  • @Gufran:同意。这并不是说 TDD 背后的概念没有用或有趣,只是它们不是灵丹妙药。

标签: unit-testing tdd


【解决方案1】:

TDD 是一种最佳实践,而不是一种宗教。如果您的案例是微不足道/显而易见的,只需编写正确的代码,而不是进行一些人为的无用重构

【讨论】:

    【解决方案2】:

    如果这个功能真的很简单,就像加法示例一样,我不会担心只用一个测试来实现它。在更复杂的实际情况下,一次实施整个解决方案可能会使您的代码结构不佳,因为您没有机会改进设计和重构代码。 TDD 是一种设计技术,可帮助您编写更好的代码,并要求您不断思考,而不仅仅是死记硬背。

    【讨论】:

      【解决方案3】:

      如果你写了一个通过测试,你就不是在做 TDD。

      这并不是说测试没有价值,而是非常清楚地表明您的新测试不是“驱动设计”(或“驱动开发”,“其他”DD)。您可能需要新的回归测试或满足管理或提出一些代码覆盖率指标,但您不需要它来推动您的设计或开发。

      你确实违反了鲍勃叔叔的第三条规则,因为你写的逻辑比你通过测试所需的要多。

      打破规则是可以的;打破规则并说你没有违反它们是不行的。如果您想严格遵守 Bob 大叔定义的 TDD,您需要遵循他的规则。

      【讨论】:

        【解决方案4】:

        在编写测试之后,要遵守鲍勃叔叔的规则中的第 3 条规则:

        public function it_can_add_numbers()
        {
            $this->add(2, 3)->shouldReturn(5);
        }
        

        正确的代码是:

        public function add($numberOne, $numberTwo)
        {
            return 5;
        }
        

        现在,当您添加第二个测试时,它失败,这将使您更改代码以符合两个测试,然后将其重构为 DRY,从而导致:

        public function add($numberOne, $numberTwo)
        {
            return $numberOne + $numberTwo;
        }
        

        我不会说这是编写代码的“唯一正确的方式”,@Oli 和@Leo 有一个观点,你不应该因为你是 TDDing 而停止像程序员一样思考,但上面的过程是一个例子遵循您在 TDD 中所述的 3 条规则...

        【讨论】:

        • 我在我的问题中提到了这种情况。我知道第一次迭代中的正确解决方案是返回 5,但是如果您第一次无法考虑怎么办?如果它从来没有引起您的注意并且您从不关心它,因为它一开始就不是正确的答案怎么办?并且因为它从来都不是正确的答案,所以你以后永远不会想到同样的事情。
        • 纯 TDDers 强迫自己这样思考。您可以对功能齐全的方法提出同样的问题(主要是因为您在开始编写测试之前就已经在脑海中设计了它)。如果你写的东西不是通过测试所需的最低要求 - 这不是纯粹的 TDD。
        • 有道理。所以这是属于纯粹主义者的东西?或者可能是代码气味(或测试气味!)之类的东西?因为如果一个测试在没有经过迭代的情况下通过,它就毫无价值(它违反了规则 2),如果一个超级重要的测试看似毫无价值,那么我应该停下来想想可能的缺陷,然后再继续吗?
        猜你喜欢
        • 2018-12-02
        • 2012-11-17
        • 2013-03-18
        • 1970-01-01
        • 1970-01-01
        • 2011-01-22
        • 2014-09-27
        • 1970-01-01
        相关资源
        最近更新 更多