NUnit-如何测试实现特定接口的所有类

时间:2020-03-05 18:46:18  来源:igfitidea点击:

如果我有接口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]类的层次结构如何?将通用测试代码放在基础测试类中,并将其继承到子测试类中。