【问题标题】:Is unit testing the definition of an interface necessary?是否需要对接口的定义进行单元测试?
【发布时间】:2010-03-16 22:36:44
【问题描述】:

我偶尔听到或读到有人在单元测试中断言他们的接口。我并不是说要模拟一个接口以用于另一种类型的测试,而是专门创建一个测试来伴随该接口。

考虑一下这个极其蹩脚的即兴例子:

public interface IDoSomething
{
   string DoSomething();
}

和测试:

[TestFixture]
public class IDoSomethingTests
{
   [Test]
   public void DoSomething_Should_Return_Value()
   {
        var mock = new Mock<IDoSomething>();
        var actualValue = mock.Expect(m => m.DoSomething()).Returns("value");

        mock.Object.DoSomething();
        mock.Verify(m => DoSomething());
        Assert.AreEqual("value", actualValue);
   }
}

我想这个想法是使用测试来驱动界面的设计,同时为实现者提供预期的指导,以便他们自己绘制好的测试。

这是一种常见(推荐)的做法吗?

【问题讨论】:

    标签: c# unit-testing mocking interface


    【解决方案1】:

    在我看来,仅使用模拟框架测试接口只测试模拟框架本身。我个人不会花时间在任何事情上。

    我会说驱动界面设计的应该是所需的功能。我认为仅使用模拟框架很难确定这一点。通过创建接口的具体实现,需要或不需要什么将变得更加明显。

    我倾向于这样做的方式(我绝不认为这是推荐的方式,只是 my 方式)是针对具体类型编写单元测试,并在 where需要 用于依赖注入目的。

    例如,如果被测试的具体类型需要访问某个数据层,我将为该数据层创建一个接口,为该接口创建一个模拟实现(或使用模拟框架),注入模拟实现并运行测试。在这种情况下,接口只是为数据层提供抽象。

    【讨论】:

    • 所以您更倾向于围绕具体(可能是半永久)类型创建测试,然后从那里提取接口和基本类型?
    • @HackedByChinese:是的,类似的。我已经扩展了我的答案,这可能会更清楚地说明我的推理。
    • 单元测试应该测试一个具体的功能。在您的示例中,接口不返回“值”,它返回任何字符串。你的测试真正做的是验证如果你告诉模拟框架它应该返回“值”,那么模拟确实返回“值”。它并没有告诉你关于接口或其任何具体实现的任何“有用”信息 - 如果模拟接口的行为符合您的预期,它会通过或失败。
    【解决方案2】:

    我从未见过这样的事情,但这似乎毫无意义。您可能希望测试这些接口的实现,而不是接口本身。

    【讨论】:

      【解决方案3】:

      接口是关于精心设计的合约,而不是精心实施的合约。由于 C# 不是一种允许接口在运行时未实现的动态语言,因此这种测试不适合该语言。如果是 Ruby 或 Perl,那么也许……

      合同是一个想法。一个想法的合理性需要在设计时进行审查,而不是运行时或测试时。

      实现可以是一组“功能性”的空存根。那仍然可以通过“接口”测试,但是合同的执行很差。但这并不意味着合同不好。

      关于完成的所有特定接口测试都是对原始意图的提醒,它只需要您在意图改变时更改 2 个地方的代码。

      【讨论】:

        【解决方案4】:

        如果您的接口的实现者可以合理地期望通过可测试的黑盒级别要求,这是一种很好的做法。在这种情况下,您可以创建一个特定于接口的测试类,用于测试该接口的实现。

        public interface ArrayMangler
        {
            void SetArray (Array myArray);
            Array GetSortedArray ();
            Array GetReverseSortedArray();
        }
        

        您可以为 ArrayMangler 编写通用测试,并验证 GetSortedArray 返回的数组确实是排序的,并且 GetReverseSortedArray 确实是反向排序的。

        然后在测试实现 ArrayMangler 的类时可以包含这些测试,以验证是否满足合理预期的语义。

        【讨论】:

        • “界面的功能”是什么意思?
        • 不符合合约会导致编译器错误,因此对合约进行单元测试是多余的。
        • @Jørn Schou-Rode 和 Todd Benning。我的措辞不好。重写以澄清我的想法。我期待看到我能获得多少反对票(只要建设性的 cmets 继续):)
        • 也许吧,但您是否仍然需要知道实现的细节才能验证数组是否正确排序。我仍然觉得单元测试应该针对每个实现。如果您能够为接口编写通用单元测试,那么也许您应该考虑将其设为抽象基类或类似的东西。
        • @Todd Benning:我的例子有灰色区域。包含什么数据类型,它是如何排序的?如果我指定了一个无符号整数数组,那么可以对排序做出合理的假设。我试图说明的基本点是,在某些情况下,接口所隐含的语义是清晰的,并且与实现的细节分开。此外,由于接口本身不提供任何功能,并且必须针对实现该接口的类的实例运行测试,因此抽象基类是不合适的。
        【解决方案5】:

        在我看来不是要走的路。创建接口是作为重构(提取接口)而不是 TDD 的行为。因此,您首先使用 TDD 创建一个类,然后提取一个接口(如果需要)。

        【讨论】:

        • 我明白了,所以你是按照我在@Fredrik Mörk 回答中的评论思路思考的?
        • @HackedByChinese 是的,但我认为我的回答更简洁。
        【解决方案6】:

        编译器自己进行接口的验证。 TDD 对接口进行验证

        您可能想查看 c# 4 中的代码协定,因为您在表述问题的方式上有点接近该区域。您似乎将几个概念捆绑在一起,您感到困惑是可以理解的。

        您的问题的简短回答是您可能听错了/误解了它。 TDD 将推动接口的发展。

        TDD 通过验证是否实现了覆盖而不涉及具体类型(实现接口的特定类型)来测试接口。

        我希望这会有所帮助。

        【讨论】:

          【解决方案7】:

          接口是关于对象之间的关系,这意味着您不能在不知道调用它的上下文的情况下“测试”接口。在调用接口的对象上使用 TDD 时,我使用接口发现,因为该对象需要来自其环境的服务。我不认为接口只能从类中提取,但这是另一个(并且更长的)讨论。

          如果您不介意广告,我们的书中还有更多内容:http://www.growing-object-oriented-software.com/

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2020-10-30
            • 1970-01-01
            • 2011-03-08
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-11-06
            相关资源
            最近更新 更多