【问题标题】:how to avoid returning mocks from a mocked object list如何避免从模拟对象列表返回模拟
【发布时间】:2011-02-10 07:30:54
【问题描述】:

我正在尝试模拟/责任驱动设计。在对象需要服务来检索其他对象的情况下,我似乎无法避免从模拟返回模拟。

一个例子可以是检查上个月的账单是否已支付的对象。它需要一个检索账单列表的服务。所以我需要在我的测试中模拟那个 billRetrievalService 。同时我需要 BillRetrievalMock 返回模拟的 Bills(因为我不希望我的测试依赖于 Bill 实现的正确性)。

我的设计有缺陷吗?有没有更好的方法来测试这个?或者这是使用查找器对象时需要的方式(在这种情况下查找账单)?

旁注:尽管 Bill 可能是一个值对象候选者,但当集合不包含值对象(例如用户)时,更广泛的问题仍然存在。

【问题讨论】:

  • 请注意,最后一个标签太长,因此被截断(并且还有一个错字)。
  • 您是退回模拟账单还是只是存根?返回存根列表的服务对我来说似乎完全可以接受。
  • @Mathias 那些是模拟我的对象需要对它们进行(模拟)方法的结果。

标签: unit-testing tdd mocking


【解决方案1】:

模拟返回的模拟是一种强烈的代码气味 - 可能是设计问题。可能是 Bills 应该是不可变的值对象,不应该被嘲笑。或者对类的设计和职责有些混淆。

mock 对象的发明者的书Growing Object-Oriented Software, Guided by Tests 和论文Mock Roles, not Objects 值得一读。

【讨论】:

  • 我拥有这本书的副本并且已经阅读了论文。我想避免的是失败的测试,我想知道失败是否来自账单中的 isPaid 逻辑。当值对象非常简单时,不模拟值对象似乎是合理的。
【解决方案2】:

通常,当我模拟时,我最终会得到三个对象。第一个对象是协调器BillsPaidLastMonthCoordinator,该对象有两个依赖项BillRetrievalServiceBillPaidValidator

您将模拟这两个依赖项,您的测试将用于检索和将账单传递给验证的交互。所以对于这个测试,你不会关心数据是什么。这有助于分离职责。您的原始对象负责检索 Bills,然后查看它是否是 isPaid Bill。

按照您描述问题的方式,您最终可能会遇到一个嘈杂而脆弱的测试。脆性来自于它能够以两种方式被破坏。

使用协调器,如果Bill 实现发生变化,它不必更改,只需实际使用Bill 的对象即可。我的 2centavos。

[编辑]

这更符合使用事件处理程序(协调器)

【讨论】:

    【解决方案3】:

    作为Way of the Testuvius 的建议,没有原则,无论多么好,都应该被绝对接受,所以你不应该需要模拟返回模拟的规则也是如此,在某些情况下这是非常合适的。

    正如 Gutzofter 建议的那样,您可以将对象分成两部分,一个用于实际验证,另一个用于检索要验证的票据。这种“单一职责”原则应用的优点是验证器将更加通用和可重用。另一方面,如果您只有这个简单的用例,并且没有特别需要更高的可重用性,那么将检索和验证保留在单个类中是非常实用的。分层,对象数量的爆炸等,没有真正的需要和真正的利益,只是为了满足抽象原则而做的,是不好的。您总是需要权衡利弊,而现实很少像我们希望的那样简单和美丽 :-) 这种实用方法的很好的例子在 Adam Bien 的 Real World Java EE Patterns - Rethinking Best Practices 中。

    【讨论】:

      【解决方案4】:

      大多数时候,如果我需要一个 mock 来返回另一个 mock,我会发现一个在另一个方向上更有意义的依赖项。换一种说法,mock-returning-mock 通常表示违反了依赖倒置原则。

      一个常见的例外:创建对象的工厂(与每次仅返回相同对象的“持有者”相反)。如果我需要在我的一生中创建多个相同类型的对象,那么我可能需要依赖ObjectFactory 并调用#createObject(),然后可能对对象设置期望。即便如此,我还是会质疑这一点。调用堆栈上一层的其他东西可能会为我创建 Objects 并根据需要将它们提供给我。

      ObjectHolder 的情况下,我更愿意直接依赖Object,而不是依赖ObjectHolder 来获取Object,并强制我的调用者将它给我它想要的任何东西。这尊重了上下文独立的理想设计属性。

      此问题的一个特定版本是“虚拟时钟”模式。有时您需要依赖虚拟时钟,但通常最好简单地要求一个时间戳(“即时请求”模式),或者在最坏的情况下,要求一个时间戳流,无论它来自哪里。测试可以提供方便的硬编码时间戳的受控流,但也可以轻松地将系统时钟转换为时间戳流。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-01-08
        • 2022-12-03
        相关资源
        最近更新 更多