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通过另一个对象上的属性测试该属性......这是一个真正的单元测试还是我应该添加一个测试(s ) 直接测试HasItemsThatRequireDomainVerification

我的感觉如何?我认为它不会增加太多价值。我认为这会增加套件的维护负担,需要时间,并且在进行未来更改时不会真正给我们更多信心。

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

想法?

4

3 回答 3

8

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

但是,在您的示例中,我认为情况并非如此。您所做的一切与提取方法非常相似。您将现有逻辑合并到另一个位置并从现有位置调用它。现有的测试仍在测试这一点。

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

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

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

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

与所有事情一样,答案始终是“视情况而定”。所有的代码都应该被测试,但每一行代码都不需要自己的测试。例如,假设我有一个类的属性:

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

我是否需要编写一个实例化 a 的测试,向它Customer写入一个Name值,然后断言它可以读回相同的值?显然不是。如果这失败了,那就是大错特错了。然而,这行代码应该被测试覆盖吗?绝对地。某处应该有一个使用Customer's的测试Name。如果没有,如果系统中没有测试使用这个属性,那么要么测试不完整,要么系统实际上不需要这个属性,应该删除。

换句话说,您在编写测试时并没有真正测试代码。您正在测试系统的功能。实现该功能的代码与测试是分开的并且平行于测试。两人不需要知道彼此的很多细节。如果某些东西的外部可见功能发生了变化,测试应该改变以匹配(并验证)它。如果外部可见的功能没有改变,测试也不应该改变。他们仍应验证相同的功能。

于 2013-07-16T14:39:56.157 回答
4

当你在做 TDD 的时候,你的测试应该是在功能级别的“功能测试”,所以只要功能没有改变,你就不应该改变你的测试。

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

TDD 不应导致您获得 100% 的覆盖率。

另一方面,如果您将单元测试用作代码解释或希望获得 100% 的覆盖率(我们在这里说的是单元测试,因为它们应该只针对一段代码),那么这些单元测试应该在每次实现适用于涵盖所有情况,但这不是 TDD 的目标。

于 2013-07-16T14:36:13.753 回答
2

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

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

于 2013-07-16T14:38:43.557 回答