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

发布于 2024-07-04 05:27:32 字数 157 浏览 8 评论 0原文

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

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

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

If I have interface IFoo, and have several classes that implement it, what is the best/most elegant/cleverest way to test all those classes against the interface?

I'd like to reduce test code duplication, but still 'stay true' to the principles of Unit testing.

What would you consider best practice? I'm using NUnit, but I suppose examples from any Unit testing framework would be valid

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

知你几分 2024-07-11 05:27:32

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

让我们走一条更聪明的路线吧; 如果您的目标是避免代码和测试代码重复,您可能需要创建一个抽象类来处理重复出现代码。

例如,您有以下接口:

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”等词实现的任何内容都是由模板方法调用的。

If you have classes implement any one interface then they all need to implement the methods in that interface. In order to test these classes you need to create a unit test class for each of the classes.

Lets go with a smarter route instead; if your goal is to avoid code and test code duplication you might want to create an abstract class instead that handles the recurring code.

E.g. you have the following interface:

public interface IFoo {

    public void CommonCode();

    public void SpecificCode();

}

You might want to create an abstract class:

public abstract class AbstractFoo : IFoo {

    public void CommonCode() {
          SpecificCode();
    }

    public abstract void SpecificCode();

}

Testing that is easy; implement the abstract class in the test class either as an inner class:

[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());
    }
}

...or let the test class extend the abstract class itself if that fits your fancy.

[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());
    }        

}

Having an abstract class take care of common code that an interface implies gives a much cleaner code design.

I hope this makes sense to you.


As a side note, this is a common design pattern called the Template Method pattern. In the above example, the template method is the CommonCode method and SpecificCode is called a stub or a hook. The idea is that anyone can extend behavior without the need to know the behind the scenes stuff.

A lot of frameworks rely on this behavioral pattern, e.g. ASP.NET where you have to implement the hooks in a page or a user controls such as the generated Page_Load method which is called by the Load event, the template method calls the hooks behind the scenes. There are a lot more examples of this. Basically anything that you have to implement that is using the words "load", "init", or "render" is called by a template method.

浮华 2024-07-11 05:27:32

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

有一个关于按合同设计的早期问题,这可以帮助您为干燥这些测试的一种方法指明正确的方向。

如果您不想要合同的开销,我建议测试设备,遵循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
  }
}

I disagree with Jon Limjap when he says,

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.

There could be many parts of the contract not specified in the return type. A language-agnostic example:

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);

}

Your unit tests on LinkedList, ArrayList, CircularlyLinkedList, and all the others should test not only that the lists themselves are returned, but also that they have been properly modified.

There was an earlier question on design-by-contract, which can help point you in the right direction on one way of DRYing up these tests.

If you don't want the overhead of contracts, I recommend test rigs, along the lines of what Spoike recommended:

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
  }
}
您的好友蓝忘机已上羡 2024-07-11 05:27:32

我认为这不是最佳实践。

简单的事实是,接口只不过是实现方法的契约。 它不是 a.) 该方法应该如何实现和 b.) 该方法应该准确执行什么操作(它只保证返回类型)的契约,这是我收集的两个原因成为您想要进行此类测试的动机。

如果您确实想控制方法实现,则可以选择:

  • 将其实现为抽象类中的方法,并从中继承。 您仍然需要将它继承到一个具体的类中,但您确信除非显式重写该方法,否则该方法将执行正确的操作。
  • 在 .NET 3.5/C# 3.0 中,将该方法实现为引用接口示例的扩展方法

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

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

I don't think this is best practice.

The simple truth is that an interface is nothing more than a contract that a method is implemented. 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.

If you really want to be in control of your method implementation, you have the option of:

  • Implementing it as a method in an abstract class, and inherit from that. You will still need to inherit it into a concrete class, but you are sure that unless it is explicitly overriden that method will do that correct thing.
  • In .NET 3.5/C# 3.0, implementing the method as an extension method referencing to the Interface

Example:

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

Any implementation properly referencing to that extension method will emit precisely that extension method so you only need to test it once.

雾里花 2024-07-11 05:27:32

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

How about a hierarchy of [TestFixture]s classes? Put the common test code in the base test class and inherit it into child test classes..

被翻牌 2024-07-11 05:27:32

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

  • 对于 xUnit.net,我创建了一个 类型解析器 库,用于搜索特定类型的所有实现(xUnit.net 扩展只是类型解析器功能的薄包装,因此可以对其进行调整以供使用在其他框架中)。
  • MbUnit 中,您可以使用 CombinatorialTest 参数上带有 UsingImplementations 属性。
  • 对于其他框架,基类模式 提到的 Spoike 可能很有用。

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

When testing an interface or base class contract, I prefer to let the test framework automatically take care of finding all of the implementers. This lets you concentrate on the interface under test and be reasonably sure that all implementations will be tested, without having to do a lot of manual implementation.

  • For xUnit.net, I created a Type Resolver library to search for all implementations of a particular type (the xUnit.net extensions are just a thin wrapper over the Type Resolver functionality, so it can be adapted for use in other frameworks).
  • In MbUnit, you can use a CombinatorialTest with UsingImplementations attributes on the parameters.
  • For other frameworks, the base class pattern Spoike mentioned can be useful.

Beyond testing the basics of the interface, you should also test that each individual implementation follows its particular requirements.

电影里的梦 2024-07-11 05:27:32

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

I don't use NUnit but I have tested C++ interfaces. I would first test a TestFoo class which is a basic implementation of it to make sure the generic stuff works. Then you just need to test the stuff that is unique to each interface.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文