如果我有接口IFoo,并且有几个实现它的类,那么针对该接口测试所有这些类的最佳/最优雅/最聪明的方法是什么?
我想减少测试代码重复,但仍然坚持单元测试的原则。
您认为最佳做法是什么?我正在使用NUnit,但我想任何单元测试框架中的示例都是有效的
答案 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。)该方法应该做什么(它只保证返回类型),我收集的两个原因会是你想要进行这种测试的动机。
如果您真的希望控制方法实现,可以选择:
示例:
public static ReturnType MethodName (this IMyinterface myImplementation, SomeObject someParameter)
{
//method body goes here
}
任何正确引用该扩展方法的实现都会精确地发出该扩展方法,因此您只需要测试一次。
答案 3 :(得分:1)
在测试接口或基类合同时,我更愿意让测试框架自动负责查找所有实现者。这使您可以专注于测试的接口,并合理地确保所有实现都将被测试,而无需进行大量的手动实现。
CombinatorialTest
UsingImplementations
属性。除了测试界面的基础知识之外,您还应该测试每个单独的实现是否符合其特定要求。
答案 4 :(得分:1)
@Emperor XLII
我喜欢MbUnit中组合测试的声音,我尝试过使用NUnit的抽象基类接口测试技术,虽然它确实有效,但你需要为一个类实现的每个接口都有一个单独的测试夹具(因为在C#中没有多重继承 - 尽管可以使用内部类,这非常酷。 实际上这很好,甚至可能是有利的,因为它通过接口将实验类的测试分组。但如果框架更聪明,那将是件好事。如果我可以使用一个属性将一个类标记为接口的“官方”测试类,那么框架将在测试中搜索所有实现该接口的类,并在其上运行这些测试。
那会很酷。
答案 5 :(得分:1)
[TestFixture]类的层次结构怎么样?将通用测试代码放在基础测试类中,并将其继承到子测试类中。
答案 6 :(得分:0)
我没有使用NUnit,但我测试过C ++接口。我首先测试一个TestFoo类,它是它的基本实现,以确保通用的东西工作。然后你只需要测试每个接口独有的东西。