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

C# 在TDD重构后编写更多的单元测试,c#,unit-testing,tdd,C#,Unit Testing,Tdd,这是我们反复讨论过的事情,人们对此的看法似乎有很大分歧 基本问题是,在进行TDD时,是否应该在周期的重构步骤之后添加额外的单元测试。我说的不是开始下一个周期的下一个测试,而是覆盖由于重构而产生的任何更改的测试 这最好用一个真实的例子来解释。TDD循环绿色后,我们有以下代码: public bool ShouldVerifyDomain { get { return this.Orders.SelectMany(x =>

这是我们反复讨论过的事情,人们对此的看法似乎有很大分歧

基本问题是,在进行TDD时,是否应该在周期的重构步骤之后添加额外的单元测试。我说的不是开始下一个周期的下一个测试,而是覆盖由于重构而产生的任何更改的测试

这最好用一个真实的例子来解释。TDD循环绿色后,我们有以下代码:

    public bool ShouldVerifyDomain
    {
        get
        {
            return this.Orders.SelectMany(x => x.Items).Any(x => x.Product.RequiresDomainVerification);
        }
    }
现在,我看了这个,然后想,嗯,linq语句可能会更整洁,更容易阅读,不会太违反Demeter,让我们重构它。因此,我在订单上创建了以下内容:

 public bool HasItemsThatRequireDomainVerification
 {
     get
     {
          return this.Items.Any(x => x.Product.RequiresCascadeDomainVerification);
     }
 }
并将
ShouldVerifyDomain
修改为:

  public bool ShouldVerifyDomain
  {
      get
      {
           return this.Orders.Any(x => x.HasItemsThatRequireDomainVerification);
      }
  }
好了,看起来好多了,我更高兴了。让我们继续我列表中的下一个测试…但是…等等,我们现在通过另一个对象上的属性测试属性
HasItemsThatRequiredMainVerification
。这是真正的单元测试还是我应该添加一个测试来直接测试
HasItemsThatRequiredMainVerification

我的感觉?我不认为这会增加多少价值。我认为这会增加套房的维护负担,需要时间,而且在将来进行更改时不会给我们带来更多的信心

它会给我们什么?
订单的公共界面的“文档”


想法?

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

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

TDD不应导致您拥有100%的保险范围


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

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


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

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

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

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

  • 您的代码需要这些代码路径吗?如果是这样,测试是不够的。您应该退出重构,为新代码路径添加失败的测试,然后添加新代码路径
  • 如果您的代码不需要这些代码路径,那么为什么它们会存在?摆脱他们
您所问的问题与一个关于测试覆盖率的古老问题非常相似,这个问题有多种形式:

  • 我应该测试私人会员吗
  • 我应该为每个方法编写一个单独的测试吗
  • 每个对象的每个成员都应该进行测试吗
就像所有事情一样,答案总是“视情况而定”。所有的代码都应该测试,但每一行代码都不需要自己的测试。例如,假设我在一个类上有一个属性:

public class Customer
{
    public string Name { get; set; }
}
我是否需要编写一个测试来实例化
客户
,向其写入
名称
值,然后断言它可以读回相同的值?显然不是。如果失败了,那么就有严重的问题。然而,测试是否应该涵盖这一行代码?绝对地某个地方应该有一个使用
客户
姓名
的测试。如果没有,如果系统中没有任何测试使用此属性,则测试不完整,或者系统实际上不需要此属性,应将其删除

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