Ожидания вызова метода PHPUnit против утверждений

Создание фиктивных классов обычно включает в себя настройку ожиданий вызова метода для двойников mocks/test.

Например. в «ванильном» PHPUnit мы можем заглушить вызов метода и установить ожидания следующим образом:

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

В структуре фиктивных объектов Mockery мы получаем такой API:

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

Подобные ожидания часто закладываются на этапе настройки набора тестов, например. setUp() метод \PHPUnit_Framework_TestCase.

Ожидания, подобные представленным выше, не выдержат испытания, если они не оправдаются. Следовательно, делая ожидания реальными утверждениями.

Это приводит к ситуации, когда у нас есть утверждения (утверждения + ожидания), разбросанные по классу тестового примера, поскольку в конечном итоге мы получаем фактические утверждения на этапе настройки тестового примера, а также в отдельных тестах.

Будет ли хорошей практикой проверять ожидания вызова метода в «обычном» методе 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. Это должно быть реализовано.

Короче говоря, должны ли мы рассматривать объявления ожиданий как фактические утверждения и, следовательно, проверять их в наших методах тестирования?


person luqo33    schedule 04.06.2016    source источник


Ответы (1)


Вам не нужно настраивать тестовые двойники (заглушки/моки) в методе setUp().

На самом деле, я бы никогда не стал настраивать мокап в коде установки теста, а помещал бы туда только очень распространенные заглушки. Мок-ожидания очень часто различны для каждого теста. Я бы предпочел создавать экземпляры своих тестовых двойников в коде установки, назначать их частным свойствам и настраивать их в каждом тестовом примере.

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 имеет встроенную поддержку пророчества. К счастью, пророчество поддерживает шпионов. Вот пример:

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. Удваивает узоры.

person Jakub Zalas    schedule 04.06.2016
comment
Спасибо за ценный совет. Я также обнаружил, что Mockery MockInterface также предоставляет метод shouldHaveBeenCalled(), поэтому его можно использовать аналогично тому, что вы показали с Prophecy. Также будет намного лучше тестировать ожидаемые вызовы методов отдельно от других вариантов поведения. В заключение я думаю, что можно с уверенностью сказать, что shouldHaveBeenCalled() является полноценным утверждением и требует собственного теста. - person luqo33; 04.06.2016