【问题标题】:Is there a way to write tests for a Interface and then test it against all classes that implement the test?有没有办法为接口编写测试,然后针对所有实现测试的类进行测试?
【发布时间】:2009-12-21 06:33:52
【问题描述】:

我已经检查过了..similar question 但我不相信答案...

我目前有一个仅由一个类实现的接口,但迟早会改变。我目前有一个针对接口的测试,所有测试开头都是:

IFoo foo = GetConcreteFoo()

GetConcreteFoo 类似于

IFoo GetConcreteFoo()
{
   return new ConcreteFooA();
}

但是,当我获得更多 Foo 的实现时,是否有办法让所有测试针对所有不同的具体 foo 列表运行?

我在想,如果没有办法,至少我可以将测试复制/粘贴到一个新文件中,名称为具体类,并更改 GetConcreteFoo 的返回对象...... (并将原始文件从 (IFooTests 更改为 IConcreteFooATests)。

我不认为这种方法特别糟糕.. 但它不太优雅/聪明,因为它会针对所有具体实现运行相同的测试(文件)。

有没有办法让它做到这一点?

(我正在使用 MSTests )

谢谢!

【问题讨论】:

    标签: c# unit-testing interface mstest


    【解决方案1】:

    不确定 MSTest,但我相信您可以在 NUnit 中使用 parameterised tests 执行此操作,例如使用实现类进行参数化,并使用 Activator.CreateInstance 对其进行实例化。

    然而,更深层次的问题是,你愿意吗?你没有说你的接口是什么样的,但拥有接口的通常原因是允许不同的实现。例如,如果您有一个带有 Area 属性的 IShape 接口,那么 Circle、Square 和 RorschachBlot 将采用不同的方式实现。 IShape.Area 属性的测试可以可靠地断言什么?因此,一般而言,您可以只测试类和(具体)方法。

    当然,如果您的接口旨在暗示接口规范之外的语义保证(例如,面积始终大于 0),那么您可以针对您知道的所有实现进行测试。 (对于您在创建测试时不知道的实现,您必须依靠带外沟通这些额外的要求,例如通过文档,并信任实现者遵守它们。当发布代码合同时,您将能够强加通过合同类可以更可靠地满足此类要求。)

    【讨论】:

    • +1 总体来说是一个很好的答案,但是做这种集成测试作为回归测试套件也很有价值。另一件事是,如果您有一个暗示“接口规范之外的语义保证”的接口,那么它是抽象类而不是接口的主要候选者。
    • 是的,接口确实暗示了语义保证,因为它适用于一个存储库,在该存储库中,独立于实现,getAll 获取所有内容,Add 添加一个新元素,Update 将更新一个元素等。该方法应该始终具有相同的可测试结果,与实现无关。我应该把它变成一个抽象类吗?如果我这样做了,我仍然需要针对所有实现进行测试。虽然谈论参数化测试让我想到了 PEX,但我也要检查一下。
    【解决方案2】:

    简短的回答,是的,你可以。更长的答案是,它取决于很多因素,即它在您的测试套件中的运行方式。

    基本上你可以这样做:

    public void GenericIFooTest(IFoo testFoo) {
       // each of your tests against foo here...
       Assert.IsTrue(testFoo.DoesItsThing());
    }
    

    然后您可以手动生成测试:

    public void TestConcreteAFoo() {
       IFoo aFoo = new ConcreteAFoo(param1, param2, param3);
    
       GenericIFooTest(aFoo);
    }
    public void TestConcreteBFoo() {
       IFoo bFoo = new ConcreteBFoo();
    
       GenericIFooTest(bFoo);
    }
    

    或者你可以使用反射来做到这一点。这假设您知道如何动态实例化每个 foo,如果它有一个最好的默认构造函数:

    public void TestAllFoos() {
       foreach(string assembly in Directory.GetFiles(assemblyPath, "*.dll", SearchOptions.All) {
          Assembly currentAssembly = Assembly.LoadAssembly(assembly);
    
          foreach(Type internalTypes in currentAssembly.GetTypes()) {
             if (internalTypes.IsAssignableFrom(IFoo) && !(internalTypes is IFoo)) {
                IFoo fooType = AppActivator.CreateInstance(internalTypes);
    
                GenericIFooTest(fooType);
             }
          }
       }
    }
    

    最后一部分是我上周为做同样的事情而编写的一些代码的记忆,它有点粗糙,但应该能让你开始。你当然可以使用 LINQ 来简化它,我只是不知道我脑子里的语法。

    【讨论】:

    • 你的建议都是不错的选择,我会尝试它们和参数化测试,看看我觉得最舒服的是什么:)
    • 您可以将它们与参数化测试结合起来,形成一个混合体,或许可以使用参数来加载程序集或您关心的类型。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-23
    • 2015-08-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-31
    相关资源
    最近更新 更多