PHPUnit 中的模拟与存根

Posted

技术标签:

【中文标题】PHPUnit 中的模拟与存根【英文标题】:Mocks vs Stubs in PHPUnit 【发布时间】:2018-02-08 11:31:13 【问题描述】:

我知道存根验证状态和模拟验证行为。

如何在 phpUnit 中进行模拟以验证方法的行为? phpunit没有验证方法(verify()),也不知道怎么弄一个moks是PHPUnit

在文档中,对创建存根进行了很好的解释:

// Create a stub for the SomeClass class.
$stub = $this->createMock(SomeClass::class);

// Configure the stub.
$stub
    ->method('doSomething')
    ->willReturn('foo');

// Calling $stub->doSomething() will now return 'foo'.
$this->assertEquals('foo', $stub->doSomething());

但在这种情况下,我正在验证状态,说返回答案。

如何创建模拟和验证行为的示例?

【问题讨论】:

文档explains on mocks同样的井 【参考方案1】:

PHPUnit 曾经支持两种开箱即用的创建测试替身的方法。在遗留的 PHPUnit 模拟框架旁边,我们也可以选择预言。

在 PHPUnit 9 中删除了预言支持,但可以通过安装 phpspec/prophecy-phpunit 重新添加。

PHPUnit 模拟框架

createMock 方法用于创建三个最知名的测试替身。这是您配置对象的方式,使其成为虚拟对象、存根或模拟对象。

您还可以使用模拟构建器创建测试存根(getMockBuilder 返回模拟构建器)。这只是做同样事情的另一种方式,让您可以使用流畅的界面调整一些额外的模拟选项(请参阅the documentation 了解更多信息)。

假人

Dummy 被传递,但从未真正被调用,或者如果它被调用,它会以默认答案响应(主要是null)。它的存在主要是为了满足一个参数列表。

$dummy = $this->createMock(SomeClass::class);

// SUT - System Under Test
$sut->action($dummy);

存根

存根用于类似查询的方法 - 返回内容的方法,但它们是否被实际调用并不重要。

$stub = $this->createMock(SomeClass::class);
$stub->method('getSomething')
    ->willReturn('foo');

$sut->action($stub);

模拟

Mocks 与类似命令的方法一起使用 - 调用它们很重要,而且我们不太关心它们的返回值(命令方法通常不返回任何值)。

$mock = $this->createMock(SomeClass::class);
$mock->expects($this->once())
    ->method('doSomething')
    ->with('bar');

$sut->action($mock);

在您的测试方法完成执行后,将自动验证预期。在上面的示例中,如果 doSomething 方法没有在 SomeClass 上调用,或者使用与您配置的参数不同的参数调用它,则测试将失败。

间谍

不支持。

预言

现在 PHPUnit 开箱即用地支持 Prophecy,因此您可以将其用作传统模拟框架的替代方案。同样,这是您配置对象的方式使其成为特定类型的测试替身。

假人

$dummy = $this->prophesize(SomeClass::class);

$sut->action($dummy->reveal());

存根

$stub = $this->prophesize(SomeClass::class);
$stub->getSomething()->willReturn('foo');

$sut->action($stub->reveal());

模拟

$mock = $this->prophesize(SomeClass::class);
$mock->doSomething('bar')->shouldBeCalled();

$sut->action($mock->reveal());

间谍

$spy = $this->prophesize(SomeClass::class);

// execute the action on system under test
$sut->action($spy->reveal());

// verify expectations after 
$spy->doSomething('bar')->shouldHaveBeenCalled();

【讨论】:

你不需要在 PhpUnit 中显式揭示预言? 你确实需要!更新了答案。谢谢! :) 和createMock() 和getMockBuilder() 有什么区别,在文档中制作Mocks 使用getMockBuilder() 方法。没关系?谢谢!!! 这是一个不同的问题;)它在文档中:“createMock($type) 方法立即返回指定类型(接口或类)的测试替身对象。这个测试替身的创建是使用最佳实践默认值执行(不执行原始类的 __construct() 和 __clone() 方法)并且不会克隆传递给测试替身方法的参数。如果这些默认值不是您需要的,那么您可以使用 getMockBuilder($type) 方法使用流畅的接口自定义测试双生成。" 你当然也可以用 phpunit 模拟对象定义间谍:$spy = $this->createMock(Foo::class); $spy->expect($this->once('bar'))->with($this->identicalTo('baz'))->willReturn('qux');。如果 $spy 被用作协作者,并且从未使用相应的参数调用,则测试将失败。【参考方案2】:

假人

首先,看看假人。如果你让我记住我把车钥匙放在哪里了,一个虚拟对象就是我的样子......如果你在 phpspec 中添加一个带有类型提示的参数以获得测试替身,你得到的对象也是......然后绝对不做任何事情。因此,如果我们得到一个测试替身并且不添加任何行为并且不对其方法进行断言,则它被称为“虚拟对象”。

哦,在他们的文档中,你会看到像 $prophecy->reveal() 这样的东西。这是一个我们不需要担心的细节,因为 phpspec 会为我们解决这个问题。得分!

存根

只要你开始控制甚至一个方法的一个返回值......繁荣!这个对象突然被称为存根。来自文档:“存根是对象替身”——所有这些东西都被称为测试替身或对象替身——当放在特定环境中时,它们会以特定方式运行。这是一种奇特的说法:只要我们添加了其中一个 willReturn() 东西,它就会变成一个存根。

实际上,大部分文档都在讨论存根以及控制其行为方式的不同方法,包括我们之前看到的 Argument 通配符。

模拟

如果您继续往下阅读,您会发现下一个内容是“模拟”。当你调用 shouldBeCalled() 时,一个对象变成了一个 mock。因此,如果您想添加一个方法被调用一定次数的断言,并且您想将该断言放在实际代码之前 - 使用 shouldBeCalledTimes() 或 shouldBeCalled() - 恭喜!您的对象现在称为模拟对象。

间谍

最后,在底部,我们有间谍。 spy 与 mock 完全一样,除了它是在代码之后添加期望 - 就像 shouldHaveBeenCalledTimes() 一样。

https://symfonycasts.com/screencast/phpspec/doubles-dummies-mocks-spies

【讨论】:

以上是关于PHPUnit 中的模拟与存根的主要内容,如果未能解决你的问题,请参考以下文章

PHPUnit 方法调用期望与断言

PHPUnit:使用注解返回特定类型真的有必要吗?

在 Laravel 5.3 项目中使用 PHPUnit 存根类方法调用的问题

PHPUnit 中的模拟 - 具有不同参数的同一方法的多个配置

PHPUnit:如何使用多个参数模拟多个方法调用?

PHPUnit 测试中 Yii2 中的模拟视图助手