重构单元测试代码的实践

时间:2010-07-17 19:59:13

标签: c# unit-testing testing

假设我有这样的功能:

public void AddEntry(Entry entry)
{
    if (entry.Size < 0)
        throw new ArgumentException("Entry size must be greater than zero");
    DB.Entries.Add(entry);
}

并进行相应的单元测试:

[TestMethod]
[ExpectedException(typeof(ArgumentException), "Entry size must be greater than zero")]
public void AddEntry_TermSizeLessThanZero_ThrowException()
{
    Entry e = new Entry(-5);

    AddEntry(e);
}

然后我重构了验证码:

public void AddEntry(Entry entry)
{
    Validate(entry);
    DB.Entries.Add(entry);
}

public void Validate(Entry entry)
{
    if (entry.Size < 0)
        throw new ArgumentException("Entry size must be greater than zero");
}

单元测试不再描述验证码。

在这种情况下,最好的做法是什么?我是否只是通过AddEntry离开Validate()进行测试?

编辑:澄清一下,假设我有理由将折射代码公开(在这种情况下有点做作),我想复制测试代码是否彻底?

3 个答案:

答案 0 :(得分:6)

由于您将Validate()方法设为对象的公共成员(无论出于何种原因),您当然应该为其添加单独的测试。 AddEntry的测试应保持不变。

单元测试应仅测试单元的接口,不应假设任何实现细节。因此,在这种情况下,您应该按照原样保留AddEntry的测试,因为它描述了接口的行为。但是,您不应该假设或测试AddEntry()来电Validate()

答案 1 :(得分:2)

此时我不会向Validate()添加测试,因为该代码已经被现有测试命中。我喜欢这个测试,因为它与班级的要求非常吻合。

当您在类或其他类中的其他函数中开始使用Validate()时,这会发生更改。还可以扩展Validate()以验证不同的问题,您需要解决此问题。

当Validate()有多个调用者,并且Validate()开始测试多个条件时,您可能需要:

  • 每个验证条件验证()测试
  • 每次调用Validate进行1次测试
    • 您可以通过传递其中一个失败条件来验证某人调用验证
      • 这种方法的优点是假抽象
      • 缺点是,如果Validate()标准发生变化,那些测试都可能无效
      • 考虑将生成无效输入的代码放入测试实用程序方法
    • 您也可以通过传入验证器的模拟对象来验证某人调用验证
      • 这种方法的好处是将验证的内容与正在进行验证的人进行分离,使测试与某些更改相比更加强大
      • 缺点是另一层抽象,使代码更复杂

似乎以这种方式添加测试可以保持良好的覆盖率,同时根据测试的需求数量线性扩展。

答案 2 :(得分:2)

随着此代码的发展,有两种可能性:

  1. Validate()保持简单并充当私人助手,外部呼叫者(包括测试)不应该知道。在这种情况下,Validate()不应该有自己的测试,而是通过调用它的其他方法测试其行为。

  2. Validate()变得足够复杂,您必须复制大量测试代码,以验证它应该在每个位置调用。在这种情况下,我会考虑提取一个接口+类来执行验证。然后可以在新家中彻底测试Validate(),并在IValidator的测试中使用假的AddEntry(或其他),声明必要时调用Validate()

相关问题