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

时间:2013-07-16 14:27:00

标签: c# unit-testing tdd

这是我们一遍又一遍地讨论过的事情,人们的意见在这方面似乎有很大不同。

基本问题,在进行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 个答案:

答案 0 :(得分:8)

您的重构步骤是否添加或更改了功能?如果是这样,那么这是一个无效的重构步骤。您应该先退出这些更改,然后再为新功能添加测试。

但是,在你的例子中,我认为不一定是这种情况。你所做的只是提取方法非常相似。您将现有逻辑合并到另一个位置,并从现有位置调用该位置。现有的测试仍在测试中。

在重构之后,如果您担心需要添加更多测试,那么您应该首先考虑的是测试覆盖率。如果你仍然处于100%(或者与重构前一样接近它),那么你可能仍然很好。另一方面,如果重构添加了测试未涵盖的代码路径,那么您有一些选择:

  • 您的代码是否需要这些代码路径?如果是这样,测试是不够的。您应该退出重构,为新代码路径添加失败的测试,然后添加新的代码路径。
  • 如果您的代码需要这些代码路径,那么它们为什么存在?摆脱它们。

您提出的问题非常类似于以多种形式提出的有关测试覆盖率的古老问题:

  • 我应该测试私人会员吗?
  • 我应该为每种方法编写单独的测试吗?
  • 每个对象的每个成员都应该进行测试吗?

与所有事情一样,答案总是“取决于”。应该测试所有代码,但每行代码都不需要自己的测试。例如,假设我在类上有一个属性:

public class Customer
{
    public string Name { get; set; }
}

我是否需要编写一个实例化Customer的测试,向其写入Name值,然后声明它可以读回相同的值?显然不是。如果失败则会出现深度错误。但是,这行代码是否应该通过测试覆盖?绝对。某处应该有一个使用Customer Name的测试。如果没有,如果系统中没有测试使用此属性,则测试不完整或系统实际上不需要此属性,应删除。

换句话说,当你编写测试时,你并没有真正测试代码。您正在测试系统的功能。实现该功能的代码与测试分开并且并行。这两个人不需要了解彼此的大量细节。如果某些内容的外部可见功能发生变化,则测试应更改为匹配(并验证)它。如果外部可见功能未更改,则测试也不必更改。他们仍然应该验证相同的功能。

答案 1 :(得分:4)

当您进行TDD时,您的测试应该处于“功能测试”功能级别,因此只要功能不变,您就不应该更改测试。

只要功能的输入和输出相同,更改实现或重构就被视为TDD中的详细信息。

TDD不应该让您获得100%的覆盖率。

另一方面,如果您使用单元测试作为代码说明或想要100%覆盖(我们在这里讲的是单元测试,因为它们应该只针对一段代码),那么这些单元测试应该改变每次实施都适用于所有案例,但这不是TDD的目标。

答案 2 :(得分:2)

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

我认为如果重构要求我们开始测试我们重构的新代码,我们最终会陷入困境。什么时候结束?