我正在尝试模拟/责任驱动的设计。在需要服务来检索其他对象的对象的情况下,我似乎有问题避免从模拟中返回模拟。
示例可以是检查上个月的账单是否已付款的对象。它需要一个检索账单清单的服务。所以我需要在我的测试中模拟billRetrievalService。同时我需要BillRetrievalMock返回模拟的Bills(因为我不希望我的测试依赖于Bill实现的正确性)。
我的设计有缺陷吗?有没有更好的方法来测试这个?或者这是使用finder对象时的方式(在这种情况下找到账单)?
旁注:尽管Bill可能是一个值对象候选者,但当集合不包含值对象(例如用户)时,更广泛的问题仍然存在。
答案 0 :(得分:13)
大多数情况下,如果我需要一个模拟来返回另一个模拟,我发现一个依赖在另一个方向更有意义。换句话说,mock-returning-mock通常指向违反依赖性倒置原则。
一个常见的例外:创建对象的工厂(而不是每次只返回相同对象的“holder”)。如果我需要在我的生命周期中创建多个相同类型的对象,那么我可能需要依赖ObjectFactory
并调用#createObject()
,然后可能会设置对象的期望值。即便如此,我也会质疑这一点。调用堆栈中的其他内容可能有可能为我创建Object
并根据需要将它们提供给我。
在ObjectHolder
案例中,我宁愿直接依赖ObjectHolder
并强制我的来电者给予Object
,而不是依赖于Object
来获取{{1}}。然而,对我来说它想要。这尊重上下文独立性的理想设计属性。
此问题的一个特定版本是“虚拟时钟”模式。有时您需要依赖虚拟时钟,但通常最好只需要时间戳(“瞬时请求”模式),或者最坏的情况是需要时间戳流,无论来自哪里。测试可以提供方便,硬编码时间戳的控制流,但也很容易将系统时钟转换为时间戳流。
答案 1 :(得分:11)
来自模拟对象发明者的书Growing Object-Oriented Software, Guided by Tests和论文Mock Roles, not Objects值得一读。
答案 2 :(得分:5)
作为Way of the Testuvius建议,绝对不应采取任何原则,无论如何都是好的,因此规则中你不应该需要模拟返回模拟,有时这种情况非常合适。
正如Gutzofter建议的那样,你可以将你的对象分成两个,一个用于实际验证,另一个用于检索要验证的账单。这种“仅责任单一”原则应用的优点是验证器更通用且可重复使用。另一方面,如果您只有这个简单的用例并且不需要更高的可重用性,那么将检索和验证保持在单个类中是非常实用的。仅仅为了满足抽象原则而进行的实际需要和实际利益的分层,物体数量的爆炸等是不合理的,这是不好的。你总是要权衡利弊,而现实很少是简单和美丽的:-)这种实用方法的很好的例子是Adam Bien的真实世界Java EE模式 - 重新思考最佳实践。
答案 3 :(得分:4)
通常当我模仿时,我最终会得到一个三元组的对象。第一个对象是协调器BillsPaidLastMonthCoordinator
此对象具有两个依赖项BillRetrievalService
和BillPaidValidator
。
你会模拟这两个依赖项,你的测试将是检索和传递账单到验证的交互。因此,对于此测试,您不会关心数据是什么。这有助于分离责任。您的原始对象负责检索Bills
,然后查看它是否为isPaid Bill。
按照您描述问题的方式,最终可能是一个嘈杂而脆弱的测试。脆弱性来自它能够以两种方式被打破。
使用corrdinator,如果Bill
实现发生更改,则不必更改,只需实际使用Bill
的对象即可。我的2centavos。
<强> [编辑] 强>
这与使用事件处理程序(协调程序)
更加一致