PHP 单元测试 - 使用 Mockery 模拟静态自动加载类
Posted
技术标签:
【中文标题】PHP 单元测试 - 使用 Mockery 模拟静态自动加载类【英文标题】:PHP Unit Testing - Mock static autoloaded class with Mockery 【发布时间】:2017-09-26 11:50:40 【问题描述】:我想对官方 Segment php 集成的包装类进行单元测试。因此,我必须用 Mockery 模拟 the Segment class,这样就不会有任何真正的 API 请求。
问题
要模拟的类仅包含静态方法。因此,我尝试像这样模拟它(使用alias):
$segment = Mockery::mock('alias:Segment');
这可行,但前提是类不是由作曲家自动加载的。如果我加载它 - 就像我必须为应用程序的其余部分加载一样 - 我会收到错误
Could not load mock Segment, class already exists.
(这是有道理的,因为文档声明之前不得加载别名类。)
问题
我怎样才能模拟这个(邪恶的?)类,但仍然像往常一样在我的应用程序的其余部分使用它?
【问题讨论】:
如果我理解正确的话,这听起来更像是一个设计问题,而不是“如何模拟它”的问题。您引用该库的类到底是什么样的?您是以某种方式注入依赖项还是只是直接从您的方法中直接调用库? 我也想过这个问题,但想不出解决办法。目前,我将这些方法直接称为:Segment::track(...)
。注入类没有意义,因为它只有静态方法,不是吗?
我认为这很有意义,因为这将允许您从测试中通过它的模拟版本。这在生产中似乎没有必要,因为它需要为您的类进行额外设置,但我认为这是使类可测试的常用方法。如果你有能力使用依赖注入容器(如PHP-DI),那将不是问题。
是的,额外的包装器也是一个很好的解决方案,但是您仍然必须测试该包装器并遇到同样的问题。此外,您的类已经 是 一个包装器,所以我认为这只是包装器-ception :) 这样想:您要测试的只是调用某个对象上的特定方法。做到这一点的最好方法是控制该对象是什么,并允许这样做,您只需从外部传入它,而不是在您的类中自动加载它。然后在生产环境中通过真实类,在测试用例中通过模拟。
嗯,我明白了。但这意味着我将注入 Segment 类的实例,然后在其上调用静态方法,例如 $segmentInstance->track(...)
。我认为这是不好的做法,因为它“隐藏”了我在静态上下文中调用track(...)
?
【参考方案1】:
本质上,您不能使用静态调用模拟类。
静态调用总是准确地引用要调用的类和方法,这相当于准确地指向要执行的文件和代码行(如果您假设基本的自动加载功能可用)。
执行不同代码的唯一方法是不包含原始类,而是首先加载模拟类代码。如果您有替代代码文件,或者您是否使用 Mockery 调用 eval()
,则无关紧要。两种方式都可以。
但它们也只能工作一次。您不能在以后的测试中切换回原始代码,因为每次脚本运行只能定义一次该类。并且无法切换实现(例如原始与模拟与另一个模拟)是这里的问题。
在 cmets 中也提到了解决方案:不要有具有静态方法的类。始终创建类的实例并调用动态方法。这样您就可以轻松地模拟类,但它需要先创建一个实例,并提供一种将类(或至少是模拟的类)注入到您要测试的代码中的方法。
作为一种通用模式,如果项目中没有依赖注入,我将使用此模式(有时我必须处理一些遗留问题):
public function __construct(MyClass $class = null)
$this->class = $class ?: new MyClass();
这样我就不必注入类,但我可以注入一个模拟而不是真正的类。
对于依赖注入可用的情况,构造函数将是一个非常基本的初始化器:
public function __construct(MyClass $class)
$this->class = $class;
如果您的依赖注入框架能够进行自动连接(如 PHP-DI),并且您只有一个 MyClass
,这将非常有用,因为它会自动注入而无需您定义任何东西。
【讨论】:
谢谢斯文!在我自己的代码中,我尽可能使用依赖注入,但在这种情况下,我必须模拟一个官方包(Segment PHP Integration)。这个包是以完全静态的方式编写的,所以我一直在寻找一种模拟这个类的方法——我无法改变它。 看图书馆,有几个细节不喜欢。但是,您可以通过多种方式处理这种情况。 1.您可以忽略将所有调用传递给内部实例化对象的静态类,并自己做同样的事情。 2. 你可以用一个实例化的包装器来包装静态类,然后你可以模拟它。 谢谢,很高兴知道没有比这更简单的方法了。以上是关于PHP 单元测试 - 使用 Mockery 模拟静态自动加载类的主要内容,如果未能解决你的问题,请参考以下文章
在 Laravel(流明)上使用 Mockery 模拟 Eloquent 模型不起作用
使用 Mockery Eloquent 模型模拟 Laravel