2012-02-20 46 views
7

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

我愿意查看任何框架(NUnit等)或Visual Studio扩展来完成此操作。


对于那些希望做同样的,我贴我的具体的解决方案,基于断avandeursen's accepted solution,为an answer

+1

添加了[lsp](http://stackoverflow.com/questions/tagged/lsp)作为标记,因为问题和答案适用于遵循LSP的任何类层次结构。 – avandeursen 2012-02-21 06:52:45

回答

6

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

我们假设您有一个接口Itf,其中实现类C1C2

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

ItfTest中的所有测试应该通过Itf(!)的任何实施。如果没有,您的实现不符合

因此Liskov Substitution Principle(在马丁的SOLID原则OO设计的“L”),来创建一个测试用例C1,您C1Test类可以扩展ItfTest。您的扩展应该用创建C1对象(将其注入或使用GoF factory method)替换模拟对象创建。通过这种方式,所有ItfTest个案都适用于C1类型的实例。此外,您的C1Test类可以包含特定于C1的其他测试用例。

同样对于C2。你可以重复使用更深的嵌套类和接口。

参考文献:Binder's Polymorphic Server Test pattern, and McGregor's PACT - 组件测试的并行体系结构。

+1

为了避免模拟实现,我只是将我的'ItfTest'类声明为抽象,并声明了一个'protected abstract Itf CreateInstance();'函数存根。 (请注意'ItfTest'和'C1Test'都需要'[TestClass]'属性。) – dlras2 2012-02-20 22:37:28

+1

是的,我也使用了工厂方法,因为它更简单。我在JUnit中使用了这种测试方法,其中没有'[TestClass]'属性,但超类中的'@ Test'注释被继承,导致超类测试用例在子类中重新运行。并且:感谢编辑。 – avandeursen 2012-02-21 06:47:40

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: 
} 

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

+0

你能编辑你的答案来解释我将如何测试实现这种方式?你似乎在说每个单元测试都需要一个适用于所有实现的switch语句,这不是我想要的。 – dlras2 2012-02-20 20:21:12

+0

如果您不想使用switch语句,则不需要使用switch语句。我提到你也可以使用反射。看到安东尼的答案为例。我还更新了我的帖子,以包含使用反射来实例化接口的各种具体实现的示例。 – 2012-02-20 20:51:06

+0

我可能会写这个来接受'IMyInterface'的实例,而不仅仅是名字,但仍然是+1。 – dlras2 2012-02-20 22:34:49

2

Joe's答案上展开,您可以使用类似MBUnit的RowTest的方式在NUnit中使用[TestCaseSource]属性。你可以在那里创建一个带有你的类名的测试用例源代码。然后,您可以修饰TestCaseSource属性的每个测试。然后使用Activator.CreateInstance你可以转换到界面,你会被设置。

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

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

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

    ... 
} 
1

这是根据我的具体实施关闭的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添加到这两个班,否则这将无法工作。最后,如果需要,您可以将任何特定于实现的测试(如构造函数)添加到子类。

+0

你使用特定的测试框架吗?我正在使用VS2012中包含的单元测试,并且从另一个“共享”测试项目(名称空间)继承的测试不会被拾取。 – drzaus 2013-04-30 17:06:43

+0

奇怪 - 在同一个项目中继承的测试,不同的命名空间显示正常。只有当他们在一个不同的项目中,他们没有注册。我错过了什么? – drzaus 2013-04-30 19:50:53