在TDD重构后编写更多单元测试

3
这是我们反复讨论的问题,人们的意见似乎相当不同。基本问题是,在进行TDD时,是否应该在循环重构步骤之后添加额外的单元测试。我不是在谈论下一个测试来开始下一个循环,而是指为了覆盖由于重构带来的任何更改而进行的测试。
这可能最好通过一个实际例子来解释。在TDD周期的绿色阶段之后,我们有以下代码:
    public bool ShouldVerifyDomain
    {
        get
        {
            return this.Orders.SelectMany(x => x.Items).Any(x => x.Product.RequiresDomainVerification);
        }
    }

现在,我看到这个代码并想,嗯,那个linq语句可以更加简洁易读,不会违反Demeter法则,让我们重构一下。因此,我在订单(Order)上创建了以下内容:

 public bool HasItemsThatRequireDomainVerification
 {
     get
     {
          return this.Items.Any(x => x.Product.RequiresCascadeDomainVerification);
     }
 }

ShouldVerifyDomain进行了修改,修改后的内容如下:

  public bool ShouldVerifyDomain
  {
      get
      {
           return this.Orders.Any(x => x.HasItemsThatRequireDomainVerification);
      }
  }

好的,看起来好多了,我对此感到满意。让我们继续进行我的测试清单中的下一个测试...但是...等等,现在我们正在通过另一个对象的属性测试HasItemsThatRequireDomainVerification属性....这是真正的单元测试吗,还是我应该添加直接测试HasItemsThatRequireDomainVerification的测试用例。

我的想法是什么?我认为这不会增加太多价值。我认为这会增加套件的维护负担,花费时间,而且在未来进行更改时并不能给我们更多的信心。

它可能会给我们带来什么?Order的公共接口的“文档”。

你有什么想法?

3个回答

8
如果您的重构步骤增加或更改了功能,那么这是一个无效的重构步骤。您应该撤销这些更改并首先为新功能添加测试。然而,在您的示例中,我认为情况并非一定如此。您所做的只是类似于提取方法的事情。您将现有逻辑合并到另一个位置,并从现有位置调用它。现有的测试仍然测试这个。在重构之后,如果您担心需要添加更多测试,那么您应该查看测试覆盖范围。如果您仍然保持100%(或与重构前相同),则您可能仍然很好。如果另一方面,重构添加了未被测试覆盖的代码路径,则您有一些选择: - 您的代码是否需要这些代码路径?如果是这样,则测试不足。您应该撤消重构,为新代码路径添加失败的测试,然后添加新代码路径。 - 如果您的代码不需要这些代码路径,那么它们为什么存在?摆脱他们。
您正在询问的问题非常类似于有关测试覆盖率的古老问题,已经以许多形式提出: - 我应该测试私有成员吗? - 我应该为每种方法编写单独的测试吗? - 每个对象的每个成员都应该进行测试吗?
与所有内容一样,答案总是“这取决于”。应该测试所有代码,但并不是每行代码都需要自己的测试。例如,假设我有一个类上的属性:
public class Customer
{
    public string Name { get; set; }
}

我需要编写一个测试来实例化一个Customer,将Name值写入它,并断言它能够读回相同的值吗?显然不需要。如果这个测试失败了,那么就有一些非常严重的问题。然而,这行代码是否应该被覆盖到测试中呢?绝对需要。系统中应该有一个测试,使用CustomerName属性。如果没有,如果系统中没有测试使用这个属性,那么要么测试不完整,要么这个属性实际上并不需要,应该被删除。
换句话说,当你编写测试时,你并不是在测试代码,而是在测试系统的功能。实现该功能的代码与测试是分离的且平行的。两者不需要过多地了解彼此的细节。如果某个东西的外部可见功能发生了变化,则测试应该随之改变(并验证)。如果外部可见的功能没有改变,测试也不应该改变。它们仍然应该验证相同的功能。

4
当进行TDD时,你的测试应该在功能层面上,也就是“功能测试”,只要功能不改变,你就不需要改变你的测试。
在TDD中,改变实现或重构被视为细节,只要功能的输入和输出相同即可。
TDD不应该让你达到100%的覆盖率。
另一方面,如果你将单元测试用作代码解释或想要达到100%的覆盖率(我们在这里谈论的是单元测试,因为它们应该只针对一小段代码),那么每次实现适应于覆盖所有情况时,这些单元测试应该发生改变,但这并不是TDD的目标。

2

你只是改变了语法,而不是行为。代码仍然以相同的方式工作,只是写法不同。我认为只要它仍然以相同的方式工作,你的单元测试仍然可靠。

如果重构要求我们开始测试我们重构的新代码,那么我们最终会陷入一个兔子洞。这将何时结束?


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