如果我有接口IFoo,并且有几个实现该接口的类,那么针对该接口测试所有这些类的最佳/最优雅/最聪明的方法是什么?
我想减少测试代码的重复,但仍然"忠于"单元测试的原则。
您认为最佳做法是什么?我正在使用NUnit,但我想任何单元测试框架中的示例都是有效的
如果您有类实现任何一个接口,则它们都需要在该接口中实现方法。为了测试这些类,您需要为每个类创建一个单元测试类。
让我们走一条更明智的路线;如果您的目标是避免代码和测试代码重复,则可能需要创建一个抽象类来处理重复代码。
例如您具有以下界面:
1 2 3 4 5 6 7
| public interface IFoo {
public void CommonCode();
public void SpecificCode();
} |
您可能要创建一个抽象类:
1 2 3 4 5 6 7 8 9
| public abstract class AbstractFoo : IFoo {
public void CommonCode() {
SpecificCode();
}
public abstract void SpecificCode();
} |
测试很容易;在测试类中将抽象类实现为内部类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| [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());
}
} |
...或者让测试类扩展抽象类本身(如果适合的话)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| [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 "来实现的任何事情都由模板??方法调用。
我不同意乔恩·林贾普(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.
返回类型中可能未指定合同的许多部分。与语言无关的示例:
1 2 3 4 5 6 7 8 9
| 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建议的方式安装试验台:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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
}
} |
我认为这不是最佳做法。
一个简单的事实是,接口不过是实现方法的协定。这既不是a。)如何实现该方法,又是b。)该方法应该做什么(仅保证返回类型)的契约,我收集的两个原因将是您想要这种方法的动机测试。
如果您确实希望控制方法的实现,则可以选择:
-
在抽象类中将其实现为方法并从中继承。您仍然需要将其继承到一个具体的类中,但是您可以确保除非显式重写它,否则该方法将完成正确的操作。
-
在.NET 3.5 / C#3.0中,将该方法实现为引用接口的扩展方法
示例:
1 2 3 4
| public static ReturnType MethodName (this IMyinterface myImplementation, SomeObject someParameter)
{
//method body goes here
} |
任何正确引用该扩展方法的实现都会精确地发出该扩展方法,因此您只需对其进行一次测试。
[TestFixture]类的层次结构如何?将公共测试代码放入基本测试类中,并将其继承到子测试类中。.
在测试接口或基类协定时,我更喜欢让测试框架自动寻找所有实现者。这样一来,您就可以专注于要测试的接口,并可以合理地确保所有实现都将得到测试,而无需进行大量的手动实现。
-
对于xUnit.net,我创建了一个Type Resolver库来搜索特定类型的所有实现(xUnit.net扩展只是Type Resolver功能的一个薄包装,因此可以适合在其他框架中使用)。
-
在MbUnit中,可以在参数上使用具有UsingImplementations属性的CombinatorialTest。
-
对于其他框架,提到的基类模式Spoike可能会有用。
除了测试接口的基础知识之外,您还应该测试每个单独的实现均符合其特定要求。
我不使用NUnit,但已经测试了C接口。我首先要测试一个TestFoo类,这是它的基本实现,以确保泛型类正常工作。然后,您只需要测试每个界面特有的内容即可。