NUnit - 如何测试实现特定接口的所有类

时间:2008-09-02 08:13:58

标签: c# .net unit-testing nunit

如果我有接口IFoo,并且有几个实现它的类,那么针对该接口测试所有这些类的最佳/最优雅/最聪明的方法是什么?

我想减少测试代码重复,但仍然坚持单元测试的原则。

您认为最佳做法是什么?我正在使用NUnit,但我想任何单元测试框架中的示例都是有效的

7 个答案:

答案 0 :(得分:13)

如果你有类实现任何一个接口,那么他们都需要在该接口中实现这些方法。为了测试这些类,您需要为每个类创建一个单元测试类。

让我们选择更聪明的路线;如果您的目标是避免代码和测试代码重复,您可能希望创建一个抽象类来处理重复出现的代码。

E.g。你有以下界面:

public interface IFoo {

    public void CommonCode();

    public void SpecificCode();

}

您可能想要创建一个抽象类:

public abstract class AbstractFoo : IFoo {

    public void CommonCode() {
          SpecificCode();
    }

    public abstract void SpecificCode();

}

测试很容易;在测试类中实现抽象类作为内部类:

[TestFixture]
public void TestClass {

    private class TestFoo : AbstractFoo {
        boolean hasCalledSpecificCode = false;
        public void SpecificCode() {
            hasCalledSpecificCode = true;
        }
    }

    [Test]
    public void testCommonCallsSpecificCode() {
        TestFoo fooFighter = new TestFoo();
        fooFighter.CommonCode();
        Assert.That(fooFighter.hasCalledSpecificCode, Is.True());
    }
}

...或者让测试类扩展抽象类本身,如果这符合您的想法。

[TestFixture]
public void TestClass : AbstractFoo {

    boolean hasCalledSpecificCode;
    public void specificCode() {
        hasCalledSpecificCode = true;
    }

    [Test]
    public void testCommonCallsSpecificCode() {
        AbstractFoo fooFighter = this;
        hasCalledSpecificCode = false;
        fooFighter.CommonCode();
        Assert.That(fooFighter.hasCalledSpecificCode, Is.True());
    }        

}

使用抽象类来处理接口所暗示的公共代码,可以提供更清晰的代码设计。

我希望这对你有用。


作为旁注,这是一种常见的设计模式,称为 Template Method pattern 。在上面的示例中,模板方法是CommonCode方法,SpecificCode称为存根或钩子。这个想法是任何人都可以扩展行为,而无需了解幕后的东西。

许多框架都依赖于这种行为模式,例如: ASP.NET您必须在页面中实现挂钩或用户控件(例如由Page_Load事件调用的生成的Load方法,模板方法会在后台调用挂钩。还有更多的例子。基本上,您必须实现的任何使用“load”,“init”或“render”的单词都是通过模板方法调用的。

答案 1 :(得分:11)

当他说,

时,我不同意Jon Limjap
  

这不是a。)方法应该如何实现和b。)该方法应该做什么(它只保证返回类型),我收集的两个原因是你的动机想要进行这种测试。

合同的许多部分可能未在退货类型中指定。与语言无关的例子:

public interface List {

  // adds o and returns the list
  public List add(Object o);

  // removed the first occurrence of o and returns the list
  public List remove(Object o);

}

您对LinkedList,ArrayList,CircularlyLinkedList以及所有其他人的单元测试不仅应该测试列表本身的返回,还要测试它们是否已被正确修改。

按合同设计有一个earlier question,可以帮助您指明干燥这些测试的正确方向。

如果您不想要合同的开销,我建议使用测试装备,与Spoike推荐的一致:

abstract class BaseListTest {

  abstract public List newListInstance();

  public void testAddToList() {
    // do some adding tests
  }

  public void testRemoveFromList() {
    // do some removing tests
  }

}

class ArrayListTest < BaseListTest {
  List newListInstance() { new ArrayList(); }

  public void arrayListSpecificTest1() {
    // test something about ArrayLists beyond the List requirements
  }
}

答案 2 :(得分:3)

我不认为这是最佳做法。

简单的事实是,界面只不过是实现方法的契约。它是合同中的任何一个。)方法应该如何实现和b。)该方法应该做什么(它只保证返回类型),我收集的两个原因会是你想要进行这种测试的动机。

如果您真的希望控制方法实现,可以选择:

  • 将其实现为抽象类中的方法,并从中继承。你仍然需要将它继承到一个具体的类中,但是你确定除非明确地覆盖它,否则该方法会做正确的事。
  • 在.NET 3.5 / C#3.0中,将该方法实现为引用接口的扩展方法

示例:

public static ReturnType MethodName (this IMyinterface myImplementation, SomeObject someParameter)
{
    //method body goes here
}

任何正确引用该扩展方法的实现都会精确地发出该扩展方法,因此您只需要测试一次。

答案 3 :(得分:1)

在测试接口或基类合同时,我更愿意让测试框架自动负责查找所有实现者。这使您可以专注于测试的接口,并合理地确保所有实现都将被测试,而无需进行大量的手动实现。

  • 对于xUnit.net,我创建了一个Type Resolver库来搜索特定类型的所有实现(xUnit.net扩展只是Type Resolver功能的一个薄包装器,所以它可以适应用于其他框架)。
  • MbUnit中,您可以在参数上使用CombinatorialTest UsingImplementations属性。
  • 对于其他框架,提到的基类模式Spoike可能很有用。

除了测试界面的基础知识之外,您还应该测试每个单独的实现是否符合其特定要求。

答案 4 :(得分:1)

@Emperor XLII

我喜欢MbUnit中组合测试的声音,我尝试过使用NUnit的抽象基类接口测试技术,虽然它确实有效,但你需要为一个类实现的每个接口都有一个单独的测试夹具(因为在C#中没有多重继承 - 尽管可以使用内部类,这非常酷。 实际上这很好,甚至可能是有利的,因为它通过接口将实验类的测试分组。但如果框架更聪明,那将是件好事。如果我可以使用一个属性将一个类标记为接口的“官方”测试类,那么框架将在测试中搜索所有实现该接口的类,并在其上运行这些测试。

那会很酷。

答案 5 :(得分:1)

[TestFixture]类的层次结构怎么样?将通用测试代码放在基础测试类中,并将其继承到子测试类中。

答案 6 :(得分:0)

我没有使用NUnit,但我测试过C ++接口。我首先测试一个TestFoo类,它是它的基本实现,以确保通用的东西工作。然后你只需要测试每个接口独有的东西。