【发布时间】:2013-09-15 10:03:47
【问题描述】:
为单元测试伪造依赖项(例如 Doctrine)还是使用真实的依赖项更好?
【问题讨论】:
-
你能举一个你不确定的例子吗?
-
@halfer 问题很清楚,虽然只是一行
-
@machete 我倾向于说不,一般不会伪造它们,但在某些情况下可能有意义
标签: php unit-testing
为单元测试伪造依赖项(例如 Doctrine)还是使用真实的依赖项更好?
【问题讨论】:
标签: php unit-testing
在单元测试中,您只使用一个类的真实实例,这就是您要测试的类。
应该模拟该类的所有依赖项,除非有理由不这样做。
不模拟的原因是,如果正在使用本身没有依赖关系的数据对象 - 您可以使用真实对象并测试它之后是否接收到正确的数据。
另一个不模拟的原因是如果模拟的配置太复杂 - 在这种情况下,你有理由重构代码,因为如果模拟一个类太复杂,该类的 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”的模拟实例,它什么都不做。我故意选择将之前混入模型类的“模型东西”和“会话保存东西”分开->“单一责任原则”。
这样测试真的变得更容易了。而且我想说这也是一种代码异味:如果创建和配置模拟类很复杂,或者在更改测试类时需要进行大量更改,那么是时候考虑重构了。那里有问题。
【讨论】:
preg_match() 这样的 php 原生函数都是外部依赖项(你和我在一起吗?)......你会把它们都包装起来只是为了测试吗? (我现在自己问这个 - 当然是部分功能)
模拟应该是有原因的。充分的理由是:
例如,您(通常)不会模拟 sin 或 cos 之类的标准库数学函数,因为它们不存在上述任何问题。
为什么建议避免在不必要的地方嘲笑?
一方面,模拟会增加测试的复杂性。
其次,模拟使您的测试依赖于代码的内部工作,即代码与 DOC 的交互方式。对于测试已实现算法的白盒测试是可以接受的,但对于黑盒测试来说是不可取的。
【讨论】: