NUnit-如何测试实现特定接口的所有类
如果我有接口IFoo,并且有几个实现它的类,那么针对该接口测试所有这些类的最佳/最优雅/最聪明的方法是什么?
我想减少测试代码的重复,但是仍然"恪守"单元测试的原则。
我们认为最佳做法是什么?我正在使用NUnit,但我想任何单元测试框架中的示例都是有效的
解决方案
回答
我不使用NUnit,但已经测试过C ++接口。我首先要测试一个TestFoo类,这是它的基本实现,以确保泛型类正常工作。然后,我们只需要测试每个界面特有的内容即可。
回答
如果我们有类实现任何一个接口,则它们都需要在该接口中实现方法。为了测试这些类,我们需要为每个类创建一个单元测试类。
让我们走一条更聪明的路吧。如果目标是避免代码和测试代码重复,则可能需要创建一个抽象类来处理重复代码。
例如。我们具有以下界面:
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()); } }
让抽象类处理接口所隐含的通用代码可以使代码设计更加简洁。
我希望这对我们有意义。
作为附带说明,这是一种称为"模板方法"模式的常见设计模式。在上面的示例中,模板方法是" CommonCode"方法,而" SpecificCode"被称为存根或者钩子。这个想法是,任何人都可以扩展行为,而无需了解幕后知识。
许多框架都依赖这种行为模式,例如在ASP.NET中,我们必须在页面或者用户控件中实现钩子,例如由Load事件调用的生成的Page_Load方法,模板方法在后台调用钩子。还有很多这样的例子。基本上,我们必须实现的使用单词" load"," init"或者" render"的所有内容都由模板方法调用。
回答
我认为这不是最佳做法。
一个简单的事实是,接口不过是实现方法的协定而已。这既不是a。)如何实现该方法,又是b。)该方法应该做什么(仅保证返回类型)的契约,我收集的两个原因是我们想要这种方法的动机测试。
如果我们确实想控制方法的实现,则可以选择:
- 在抽象类中将其实现为方法并从中继承。我们仍然需要将其继承到具体的类中,但是我们可以确定,除非显式重写它,否则该方法将完成正确的操作。
- 在.NET 3.5 / C#3.0中,将该方法实现为引用接口的扩展方法
例子:
public static ReturnType MethodName (this IMyinterface myImplementation, SomeObject someParameter) { //method body goes here }
正确引用该扩展方法的任何实现都将精确地发出该扩展方法,因此我们只需对其进行一次测试。
回答
我不同意乔恩·林贾普(Jon Limjap)的话,
It is not a contract on either a.) how the method should be implemented and b.) what that method should be doing exactly (it only guarantees the return type), the two reasons that I glean would be your motive in wanting this kind of test.
合同中可能有很多部分未在返回类型中指定。与语言无关的示例:
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,其他所有单元不仅应该测试列表本身是否已返回,还应该测试它们是否已正确修改。
以前有一个关于按合同设计的问题,它可以以一种干燥这些测试的方式为我们指明正确的方向。
如果我们不希望合同的开销,我建议按照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 } }
回答
在测试接口或者基类协定时,我更喜欢让测试框架自动负责查找所有实现者。这使我们可以专注于被测试的接口,并可以合理地确保所有实现都将得到测试,而无需进行大量的手动实现。
- 对于xUnit.net,我创建了一个Type Resolver库来搜索特定类型的所有实现(xUnit.net扩展只是Type Resolver功能的一个薄包装,因此可以在其他框架中使用)。
- 在MbUnit中,可以在参数上使用具有" UsingImplementations"属性的" CombinatorialTest"。
- 对于其他框架,提到的基类模式Spoike可能会有用。
除了测试接口的基础知识之外,我们还应该测试每个单独的实现均符合其特定要求。
回答
@皇帝XLII
我喜欢MbUnit中组合测试的声音,我已经尝试使用NUnit使用抽象基类接口测试技术,尽管它确实可以工作,但是我们需要为类实现的每个接口使用单独的测试装置(因为Cthere中尽管可以使用内部类,但没有多重继承,这很酷。
实际上,这是很好的,甚至可能是有利的,因为它通过接口对实现类的测试进行分组。但是,如果框架更聪明,那就太好了。如果我可以使用属性将某个类标记为接口的"官方"测试类,则框架将在被测程序集中搜索实现该接口的所有类,然后在其上运行这些测试。
那肯定很棒。
回答
[TestFixture]类的层次结构如何?将通用测试代码放在基础测试类中,并将其继承到子测试类中。