如果我有接口 IFoo,并且有几个实现它的类,那么针对该接口测试所有这些类的最佳/最优雅/最聪明的方法是什么?

我想减少测试代码重复,但仍然“忠于”单元测试的原则。

您认为最佳实践是什么?我正在使用 NUnit,但我认为任何单元测试框架中的示例都是有效的

有帮助吗?

解决方案

如果您有类实现任何一个接口,那么它们都需要实现该接口中的方法。为了测试这些类,您需要为每个类创建一个单元测试类。

让我们走一条更聪明的路线吧;如果你的目标是 避免代码和测试代码重复 您可能想创建一个抽象类来处理 再次发生的 代码。

例如。你有以下界面:

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 称为存根或钩子。这个想法是任何人都可以扩展行为而无需了解幕后的东西。

许多框架都依赖于这种行为模式,例如 网络平台 您必须在页面或用户控件中实现挂钩,例如生成的 Page_Load 调用的方法 Load 事件时,模板方法在幕后调用钩子。这方面的例子还有很多。基本上,您必须使用“load”、“init”或“render”等词实现的任何内容都是由模板方法调用的。

其他提示

我不同意 乔恩·林贾普 当他说,

它不是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 和所有其他列表的单元测试不仅应该测试列表本身是否已返回,还应该测试它们是否已被正确修改。

有一个 先前的问题 基于合同设计,这可以帮助您指明正确的方向,找到一种干燥这些测试的方法。

如果您不想要合同的开销,我推荐测试设备,类似于 斯波克 受到推崇的:

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中,将该方法实现为引用接口的扩展方法

例子:

public static ReturnType MethodName (this IMyinterface myImplementation, SomeObject someParameter)
{
    //method body goes here
}

任何正确引用该扩展方法的实现都将精确地发出该扩展方法,因此您只需测试它一次。

@四十二皇帝

我喜欢 MbUnit 中组合测试的声音,我尝试过使用 NUnit 的抽象基类接口测试技术,虽然它确实有效,但您需要为类实现的每个接口有一个单独的测试装置(因为在 C# 中)没有多重继承 - 尽管可以使用内部类,这非常酷)。实际上,这很好,甚至可能是有利的,因为它按接口对实现类的测试进行分组。但如果框架能更智能一点就好了。如果我可以使用属性将类标记为接口的“官方”测试类,那么框架将在测试下的程序集中搜索实现该接口的所有类,并在其上运行这些测试。

那会很酷。

[TestFixture]类的层次结构怎么样?将公共测试代码放在基测试类中,并将其继承到子测试类中。

当测试接口或基类契约时,我更喜欢让测试框架自动查找所有实现者。这使您可以专注于被测试的接口,并合理地确定所有实现都将被测试,而无需进行大量的手动实现。

  • 为了 xUnit.net, ,我创建了一个 类型解析器 用于搜索特定类型的所有实现的库(xUnit.net 扩展只是类型解析器功能的一个薄包装器,因此它可以适用于其他框架)。
  • 兆单位, ,你可以使用 CombinatorialTestUsingImplementations 参数上的属性。
  • 对于其他框架,基类模式 斯波克 提到的可能有用。

除了测试接口的基础知识之外,您还应该测试每个单独的实现是否遵循其特定的要求。

我不使用 NUnit,但我测试过 C++ 接口。我将首先测试一个 TestFoo 类,它是它的基本实现,以确保通用的东西可以工作。然后你只需要测试每个界面特有的东西。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top