代码之家  ›  专栏  ›  技术社区  ›  Rei Miyasaka

重构单元测试代码的实践

  •  4
  • Rei Miyasaka  · 技术社区  · 14 年前

    假设我有这样一个函数:

    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");
    }
    

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

    在这种情况下最好怎么办?我是否只需要让Validate()通过AddEntry进行测试?

    3 回复  |  直到 13 年前
        1
  •  2
  •   Frank Schwieterman    14 年前

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

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

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

    • 每次调用1个测试以验证
      • 您可以通过传入一个失败条件来验证有人调用Validate
        • 这种方法的优点是不太假抽象
        • 考虑将生成无效输入的代码放在测试实用程序方法中
      • 您还可以通过传递验证器的模拟对象来验证有人调用Validate
        • 这种方法的好处是将验证的内容与验证的执行者分离,使得测试相对于某些更改更加健壮

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

        2
  •  6
  •   Johannes Rudolph    14 年前

    自从你做了那个 Validate() AddEntry 应该留在原地。

    单元测试应该只测试单元的接口,而不应该假设任何实现细节。所以在这种情况下,你应该把你的测试留给 加法 因为它描述了界面的行为。但你不应该假设或测试 AddEntry() 电话 .

        3
  •  2
  •   dahlbyk    14 年前

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

    1. Validate() 验证() 不应该有自己的测试,而是通过调用它的其他方法测试它的行为。

    2. 变得足够复杂,您必须复制大量的测试代码来验证它是否在每个应该调用的地方都被调用。在本例中,我将考虑提取一个interface+类来执行验证。 验证() 然后就可以在它的新家里彻底测试一个假的 IValidator (或其他)可以用来测试 AddEntry ,断言 验证()