【问题标题】:Unit Testing (PHP): When to fake/mock dependencies and when not to单元测试 (PHP):何时伪造/模拟依赖项,何时不伪造
【发布时间】:2013-09-15 10:03:47
【问题描述】:

为单元测试伪造依赖项(例如 Doctrine)还是使用真实的依赖项更好?

【问题讨论】:

  • 你能举一个你不确定的例子吗?
  • @halfer 问题很清楚,虽然只是一行
  • @machete 我倾向于说不,一般不会伪造它们,但在某些情况下可能有意义

标签: php unit-testing


【解决方案1】:

在单元测试中,您只使用一个类的真实实例,这就是您要测试的类。

应该模拟该类的所有依赖项,除非有理由不这样做。

不模拟的原因是,如果正在使用本身没有依赖关系的数据对象 - 您可以使用真实对象并测试它之后是否接收到正确的数据。

另一个不模拟的原因是如果模拟的配置太复杂 - 在这种情况下,你有理由重构代码,因为如果模拟一个类太复杂,该类的 API 可能是也太复杂了。

但一般的答案是:您希望每次都模拟每个依赖项。

我会给你一个“too-complicated-so-refactor”案例的例子。

我使用“Zend_Session_Namespace”对象来存储模型的内部数据。该实例被注入到模型中,因此模拟不是问题。

但是真正的“命名空间”类的内部实现让我按照模型中使用它们的正确顺序模拟了对__set__get 的所有调用。那糟透了。因为每次我决定重新排序代码中值的读取和写入时,我都必须更改测试中的模拟,尽管没有任何问题。代码中的重构不应导致测试中断或强迫您更改它们。

重构添加了一个将“Zend_Session_Namespace”与模型分开的新对象。我创建了一个扩展“ArrayObject”并包含“命名空间”的对象。在创建时,从 Namespace 中读取所有值并将其添加到 ArrayObject,并且在每次写入时,该值也会传递给 Namespace 对象。

我现在的情况是,我可以在所有测试中使用真正的扩展 ArrayObject,它本身只需要一个未配置的“Zend_Session_Namespace”模拟实例,因为我不需要测试值是否正确存储在我测试模型时的会话。我只需要一个在模型内部使用的数据存储。

为了测试会话是否被正确读取和写入,我对该 ArrayObject 本身进行了测试。

所以最后我使用了模型的真实实例,数据存储的真实实例以及“Zend_Session_Namespace”的模拟实例,它什么都不做。我故意选择将之前混入模型类的“模型东西”和“会话保存东西”分开->“单一责任原则”。

这样测试真的变得更容易了。而且我想说这也是一种代码异味:如果创建和配置模拟类很复杂,或者在更改测试类时需要进行大量更改,那么是时候考虑重构了。那里有问题。

【讨论】:

  • 依赖树呢? A 类依赖于 B 类,B 类依赖于 C 类和 D 类,而 C 类依赖于 E 类。在我的情况下(CMS),大多数类都有多个嵌套依赖项(大多数时候是 ServiceLocators 和 ObjectManagers)。有没有一种简单的方法来模拟这种树?
  • 如果您无法控制被测系统内的对象创建,您将如何模拟?
  • 在测试 A 类时,您模拟 B 类,无需担心之后会出现多少 C、D、E、F、... Z。在测试 B 类时,您不必担心 A,并且必须模拟 C 和 D - 而且 C 和 D 的依赖关系再次超出范围。
  • @hek2mgl:这是一个应该避免的问题。在您自己的代码中这没有问题,在外部代码中您始终可以添加一个包装层来解决这个问题。
  • @Sven 首先,我真的很感谢有人回答了这个问题 :) 我可以按照你的设计方法,但我认为说模拟所有外部依赖项太极端了。 IMO 甚至像 preg_match() 这样的 php 原生函数都是外部依赖项(你和我在一起吗?)......你会把它们都包装起来只是为了测试吗? (我现在自己问这个 - 当然是部分功能)
【解决方案2】:

模拟应该是有原因的。充分的理由是:

  • 您无法轻松地使依赖组件 (DOC) 的行为符合您的测试预期。
  • 调用 DOC 是否会导致任何非确定性行为(日期/时间、随机性、网络连接)?
  • 测试设置过于复杂和/或维护密集(例如,需要外部文件)
  • 原始 DOC 为您的测试代码带来了可移植性问题。
  • 使用原始 DOC 是否会导致构建/执行时间过长而无法接受?
  • 是否存在导致测试不可靠的 DOC 稳定性(成熟度)问题,或者更糟糕的是,DOC 甚至还没有可用?

例如,您(通常)不会模拟 sincos 之类的标准库数学函数,因为它们不存在上述任何问题。

为什么建议避免在不必要的地方嘲笑?

  • 一方面,模拟会增加测试的复杂性。

  • 其次,模拟使您的测试依赖于代码的内部工作,即代码与 DOC 的交互方式。对于测试已实现算法的白盒测试是可以接受的,但对于黑盒测试来说是不可取的。

【讨论】:

    猜你喜欢
    • 2010-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-01
    • 2023-04-07
    • 1970-01-01
    相关资源
    最近更新 更多