我可以实现一系列可重用的测试来测试接口的实现吗?

时间:2012-02-20 20:04:44

标签: .net visual-studio-2010 unit-testing liskov-substitution-principle

我在C#中编写了一系列集合类,每个集合类都实现了类似的自定义接口。是否可以为接口编写单个单元测试集合,并在几个不同的实现上自动运行它们?我想避免为每个实现重复测试代码。

我愿意研究任何框架(NUnit等)或Visual Studio扩展来实现这一目标。


对于那些希望这样做的人,我将基于avandeursen's accepted solution的具体解决方案发布为an answer

4 个答案:

答案 0 :(得分:6)

是的,这是可能的。诀窍是让您的单元类测试层次结构遵循代码的类层次结构。

假设您有一个实现类ItfC1的接口C2

首先为ItfItfTest)创建一个测试类。要实际执行测试,您需要创建Itf接口的模拟实现。

ItfTest中的所有测试都应传递Itf(!)的任何实现。如果没有,那么您的实现不符合Liskov Substitution Principle(马丁的SOLID OO设计原则中的“L”)

因此,要为C1创建测试用例,您的C1Test类可以扩展ItfTest。您的扩展应该通过创建C1对象(将其注入或使用GoF factory method)替换模拟对象创建。通过这种方式,所有ItfTest个案例都会应用于C1类型的实例。此外,您的C1Test类可以包含特定于C1的其他测试用例。

同样适用于C2。你可以重复这个技巧来获得更深层次的嵌套类和接口。

参考文献:Binder的Polymorphic Server Test模式, 和McGregor的PACT - 组件测试的并行架构。

答案 1 :(得分:2)

您可以使用MBUnit中的[RowTest]属性执行此操作。下面的示例显示了向方法传递字符串以指示要实例化的接口实现类的位置,然后通过反射创建此类:

[RowTest]
[Row("Class1")]
[Row("Class2")]
[Row("Class3")]
public void TestMethod(string type)
{
   IMyInterface foo = Activator.CreateInstance(Type.GetType(type)) as IMyInterface;

   //Do tests on foo:
}

在[Row]属性中,您可以传递任意数量的输入参数,例如测试的输入值或方法调用返回的预期值。您需要将相应类型的参数添加为测试方法输入参数。

答案 2 :(得分:2)

扩展Joe's答案,你可以使用NUnit中的[TestCaseSource]属性,类似于MBUnit的RowTest。您可以在其中创建一个包含类名的测试用例源。然后,您可以装饰TestCaseSource属性的每个测试。然后使用Activator.CreateInstance,您可以转换为接口,然后设置。

像这样的东西(注意 - 头编译)

string[] MyClassNameList = { "Class1", "Class2" };

[TestCaseSource(MyClassNameList)]
public void Test1(string className)
{
    var instance = Activator.CreateInstance(Type.FromName(className)) as IMyInterface;

    ...
}

答案 3 :(得分:2)

这是我基于avandeursen's answer的具体实现:

[TestClass]
public abstract class IMyInterfaceTests
{
    protected abstract IMyInterface CreateInstance();

    [TestMethod]
    public void SomeTest()
    {
        IMyInterface instance = CreateInstance();
        // Run the test
    }
}

然后,每个接口实现都定义以下测试类:

[TestClass]
public class MyImplementationTests : IMyInterfaceTests
{
    protected override IMyInterface CreateInstance()
    {
        return new MyImplementation();
    }
}
对于从SomeTest派生的每个具体TestClass

IMyInterfaceTests运行一次。通过使用抽象基类,我避免了任何模拟实现的需要。请确保将TestClassAttribute添加到这两个类中,否则这将无效。最后,如果需要,您可以向子类添加任何特定于实现的测试(例如构造函数)。