【问题标题】:NUnit - How to test all classes that implement a particular interfaceNUnit - 如何测试所有实现特定接口的类
【发布时间】:2010-09-07 12:42:03
【问题描述】:

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

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

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

【问题讨论】:

    标签: c# .net unit-testing nunit


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

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

      让我们改用更智能的路线;如果您的目标是避免代码和测试代码重复,您可能希望创建一个抽象类来处理重复发生的代码。

      例如你有以下界面:

      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 你必须在页面或用户控件中实现挂钩,例如由Load 事件调用的生成的Page_Load 方法,模板方法在幕后调用挂钩。这方面的例子还有很多。基本上,您必须使用“load”、“init”或“render”等词来实现的任何内容都由模板方法调用。

      【讨论】:

      • @sixlettervariables:是的,我忘了添加虚拟关键字等等。我把它归咎于我在写这篇文章的同时必须做的所有 Java 编程;)
      • 不错的答案!现在 NUnit 支持通用测试类,并且 TestFixture 属性可用于提供运行测试时要使用的特定类型。我写了一个blog post,关于如何测试展示这些特性的接口的每个实现者。
      【解决方案3】:

      我认为这不是最佳做法。

      简单的事实是,接口只不过是实现方法的合同。 不是关于 a.) 如何实现该方法和 b.) 该方法应该做什么(它只保证返回类型)的合同,我收集到的两个原因是成为你想要这种测试的动机。

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

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

      例子:

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

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

      【讨论】:

        【解决方案4】:

        我不同意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
          }
        }
        

        【讨论】:

          【解决方案5】:

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

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

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

          【讨论】:

            【解决方案6】:

            [TestFixture] 类的层次结构如何?将通用测试代码放在基测试类中,继承到子测试类中..

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2012-04-16
              • 1970-01-01
              • 2015-08-19
              • 2010-10-16
              • 2019-10-16
              • 2016-05-13
              • 2014-01-13
              相关资源
              最近更新 更多