单元测试(PHP):何时伪造/模拟依赖项以及何时不依赖

时间:2013-09-15 10:03:47

标签: php unit-testing

最好伪造依赖关系(例如Doctrine)进行单元测试或使用真实的依赖关系吗?

2 个答案:

答案 0 :(得分:4)

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

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

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

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

但一般答案:您希望始终模仿每个依赖项。

我将举例说明“过于复杂的重构”案例。

我使用“Zend_Session_Namespace”对象进行模型的内部数据存储。那个实例被注入到模型中,所以模拟不是问题。

但真正的“命名空间”类的内部实现使我按照它们在模型中的使用方式的正确顺序模拟了对__set__get的所有调用。那很糟糕。因为每次我决定在我的代码中重新排序读取和写入值时,我不得不在测试中更改模拟,尽管没有任何内容被破坏。代码中的重构不应导致测试中断或迫使您更改它们。

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

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

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

所以最后我使用了模型的真实实例,以及数据存储的真实实例以及无效的“Zend_Session_Namespace”的模拟实例。我故意选择将“模型内容”和“会话保存内容”分开,这些内容之前已混合到模型类中 - > “单一责任原则”。

测试确实变得更容易了。而且我会说这也是一种代码味道:如果创建和配置模拟类很复杂,或者在更改测试类时需要进行大量更改,那么现在是时候考虑重构了。那里有些不对劲。

答案 1 :(得分:0)

应该出于某种原因进行模拟。充分的理由是:

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

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

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

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

  • 第二,模拟使测试取决于代码的内部工作原理,即代码与DOC的交互方式。对于测试了实现算法的白盒测试来说是可以接受的,而对于黑盒测试则是不希望的。