【问题标题】:Unit testing of child classes子类的单元测试
【发布时间】:2009-11-23 12:53:21
【问题描述】:

假设我们有这个超简单的类层次结构:

public class SomeMath
{
    public int Add(int x, int y)
    {
        return x + y;
    }
}

public class MoreMath : SomeMath
{
    public int Subtract(int x, int y)
    {
        return x - y;
    }
}

当我为MoreMath 类编写测试时,我应该为Add 方法编写测试吗?或者我应该只在测试SomeMath 类时关心该方法?更一般地说:我应该测试一个类的所有方法,还是应该只测试“新”方法?

我可以为双方提出一些理由。例如,在测试所有方法时,您最终会多次测试同一事物,这不是很好,而且可能会变得乏味。但是,如果您不测试所有方法,SomeMath 的更改可能会破坏MoreMath 的用法?这也将是一件坏事。我想这可能也取决于情况。就像它是否扩展了我可以控制的类。但无论如何,我是一个完全的测试新手,所以我很想知道有什么人比我想象的更聪明:-)

【问题讨论】:

  • 考虑到您提出的问题没有明确的正确或错误答案,我认为仅在 7 分钟后接受答案是个坏主意。

标签: c# unit-testing class-hierarchy


【解决方案1】:

通常我不会测试子类中的行为,除非子类改变了父类中预期的行为。如果它使用相同的行为,则无需对其进行测试。如果您打算对父类进行重大更改,但子类仍需要旧行为,那么我将首先在子类中创建测试以定义其所需的行为,然后我将在父测试和后续测试中进行更改代码更改。这遵循 YAGNI 原则——您将不需要它——并延迟子测试的实施,直到它们真正有目的为止。

【讨论】:

  • 我认为这个答案假设单元测试仅以 TDD 方式编写。在我看来,这只是答案的一半,但请参阅我的答案以进行更全面的检查。
【解决方案2】:

我只会测试 SomeMath 类的 Add 方法。如果MoreMath 只是继承它并且完全没有做任何新的事情,那么为两者编写测试将是纯粹的重复代码,仅此而已。恕我直言,对这些事情有点务实总是好的。

【讨论】:

    【解决方案3】:

    在我目前的位置,我们遇到了一个类似的问题,我们想开发接口,但要确保接口的每个实现都表现正确(例如,不同的数据层应该表现相同,而不管该层的实现如何)。我们解决它的方法(使用 NUnit)是在单元测试中拥有一个继承结构:

    public interface ICalculator
    {
      public int Add(int a, int b)
    }
    
    public class Calculator : ICalculator
    {
      public int Add(int a, int b) { return a + b; }
    }
    
    public class TestCalculatorInterface
    {
       public abstract SomeMath GetObjectToTest();
    
       [Test]
       public void TestAdd()
       {
           var someMath = GetObjectToTest();
           ...
       }
    }    
    
    [TestFixture]
    public class TestCalculator: TestCalculatorInterface
    {
       public virtual Calculator GetObjectToTest() { return new Calculator(); }
    }
    

    我们不会在基本接口测试上具有 [TestFixture] 属性,但在所有测试方法上都有 [Test] 属性。 TestCalculator 类是 [TestFixture] 类,但它继承了基类的所有测试,从而使子类仅负责提供对象以测试该接口。

    我会为您的情况采用类似的模式,因此测试针对所有类运行,但只编写一次。

    【讨论】:

      【解决方案4】:

      首先,我认为至少有两个因素可能会影响您选择其中一个的决定:

      • 继承的目的是什么?
      • 相关测试的目的是什么?

      在 TDD 场景中,我倾向于为 MoreMath 编写一个测试用例,验证它是否派生自 SomeMath,然后考虑覆盖从 SomeMath 继承的所有成员。

      但是,这意味着,从设计的角度来看,MoreMath 是从 SomeMath 派生的一个重要设计方面。如果您以多态方式使用 SomeMath,肯定会出现这种情况。但是,如果您只是使用继承进行重用,则情况并非如此(但不推荐)。

      在后一种情况下(继承用于重用),父类和子类之间没有概念上的联系,将来您可能会试图打破继承。在这种情况下,进行验证父母是否正确的测试是一种很差的保障措施。

      从质量保证 (QA) 的角度来看,每个班级的每个成员都应该经过严格的测试。这意味着即使测试代码相同,您也应该为每个子类重复测试代码,因为您需要验证没有以意外方式覆盖任何虚拟方法。但是,为了保持 DRY,您可以将它们编写为参数化测试,或者使用诸如 Pex 之类的工具。

      就我个人而言,我很少进入 QA 阶段。通常,在 TDD 期间创建的测试套件是一个足够的安全网……但这一切都取决于您构建的软件类型。

      【讨论】:

      • 把这个作为答案,因为它涵盖了更多的角度 :)
      【解决方案5】:

      我会让 MoreMath 的测试类继承自 SomeMath 的测试类,从而继承该类的所有测试。这意味着我只需为新功能编写额外的测试,但所有子类的功能都经过全面测试。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-11-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-08-26
        相关资源
        最近更新 更多