PHPUnit 方法调用期望与断言

PHPUnit method call expectations vs. assertions

创建模拟 classes 通常涉及在 mocks/test 双打上配置方法调用预期。

例如在 'vanilla' PHPUnit 中,我们可以存根方法调用并设置期望值,如下所示:

$stub->expects($this->any())->method('doSomething')->willReturn('foo');

Mockery模拟对象框架中,我们得到API是这样的:

$mock->shouldReceive('doIt')->with(m::anyOf('this','that'))->andReturn($this->getSomething());

像这样的期望通常在测试套件的设置阶段就已存在,例如\PHPUnit_Framework_TestCase.

setUp() 方法

像上面提到的那些期望如果没有实现就会破坏测试。因此,使期望成为实际断言。

这导致我们在测试用例周围散布断言(断言+期望)的情况 class 因为我们最终在测试用例的设置阶段以及在个人测试。

在 'regular' assert.. 方法中测试方法调用预期是否是一个好习惯。这可能看起来像那样(嘲笑):

public function setUp()
{
    $mock = m::mock(SomeClass::class);
    $mock->shouldReceive('setSomeValue');
    $this->mock = $mock;
}

以及稍后在其中一种测试方法结束时:

public function testSoemthing()
{ 
    ...
    $this->assertMethodCalled($this->mock, 'setSomeValue');
}

assertMethodCalled 不是 PHPUnit 公开的方法。必须实施。

简而言之,我们是否应该将期望声明视为实际断言,然后在我们的测试方法中针对它们进行测试?

您不需要在 setUp() 方法中配置测试替身 (stubs/mocks)。

实际上,我永远不会在测试设置代码中配置模拟,我只会将非常常见的存根放在那里。每个测试用例的模拟期望通常是不同的。我宁愿在设置代码中实例化我的测试替身,将它们分配给私有属性并在每个测试用例中配置它们。

private $registration;
private $repository;

protected function setUp()
{
    $this->repository = $this->getMock(UserRepository::class);
    $this->registration = new Registration($repository);
}

public function testUserIsAddedToTheRepositoryDuringRegistration()
{
    $user = $this->createUser();

    $this->repository
         ->expects($this->once())
         ->method('add')
         ->with($user);

    $this->registration->register($user);
}

所以把你的测试双重配置放在测试用例中就可以了。它实际上更好,因为您的测试用例具有所有上下文,因此更具可读性。如果您发现自己配置了很多测试替身或编写了很长的测试用例,这可能意味着您有太多协作者,您应该考虑如何改进您的设计。您还可以使用具有有意义名称的辅助方法来使您的测试用例更具可读性 - 即 $this->givenExistingUser()$this->givenRepositoryWithNoUsers().

如您所见,模拟期望(不要将它们与不是期望的存根混淆)非常接近断言。实际上有一种方法可以用另一种类型的测试替身来完成你正在寻找的东西 - 间谍。

经典的 phpunit 模拟框架不支持间谍。然而,PHPUnit 现在内置了对预言的支持。幸运的是,预言supports spies。这是一个例子:

private $registration;
private $repository;

protected function setUp()
{
    $this->repository = $this->prophesize(UserRepository::class);
    $this->registration = new Registration($repository->reveal());
}

public function testUserIsAddedToTheRepositoryDuringRegistration()
{
    $user = $this->createUser();

    $this->registration->register($user);

    $this->repository->add($user)->shouldHaveBeenCalled();
}

最后一点,我避免在同一个测试用例中使用断言和模拟期望。大多数时候,这些是独立的行为,应该用不同的测试用例来覆盖。

要了解有关测试替身的更多信息,请阅读优秀的 PHP Test Doubles Patterns