Статьи

Шпаргалка PHPUnit против Phake

Бенджамин Эберлей познакомил меня с Phake своей недавней статьей : это PHP-библиотека, готовая для Composer, которая легко интегрируется с PHPUnit и предоставляет независимую среду Test Doubles, способную создавать заглушки, издевательства и шпионы. Синтаксис и объектная модель напоминают мне о Mockito, фреймворке Java Test Double от авторов Growing Object-Oriented Software.

Мне нравятся инструменты, которые делают одно и делают это хорошо, и после экспериментов с Phake я использую их для всего нового кода. Я готовлю эту таблицу для своих коллег в Onebip, чтобы они могли сразу начать использовать Phake, а не копаться в документации.

Столбики

Заглушки определяются как Test Doubles, которые возвращают стандартные значения при вызове, но не выполняют собственных проверок и проверок. Вот пример с PHPUnit:

$stub = $this->getMock('Namespace\CollaboratorClass');
$stub->expects($this->any())
  ->method('doSomething')
  ->with('param')
  ->will($this->returnValue('returned'));

С Phake то же самое становится:

$stub = Phake::mock('Namespace\CollaboratorClass');
Phake::when($stub)->doSomething('param')->thenReturn('returned');

Для обработки нескольких вызовов одного и того же метода требуется специальный обратный вызов в PHPUnit:

$stub = $this->getMock('Namespace\CollaboratorClass');
$stub->expects($this->any())
  ->method('doSomething')
  ->will($this->returnCallback(function($param) {
  $toReturn = array(
  'param1' => 'returned1',
  'param2' => 'returned2',
  }));

С Phake, свободный интерфейс позволяет указать несколько возвращаемых значений:

$stub = Phake::mock('Namespace\CollaboratorClass');
Phake::when($stub)->doSomething('param1')->thenReturn('returned1')
Phake::when($stub)->doSomething('param2')->thenReturn('returned2');

Заглушки также могут генерировать исключения для имитации ошибки в соавторе на более низком уровне:

$stub = $this->getMock('Namespace\CollaboratorClass');
$stub->expects($this->any())
  ->method('doSomething')
  ->will($this->throwException(new InvalidArgumentException()));

С Phake:

$stub = Phake::mock('Namespace\CollaboratorClass');
Phake::when($stub)->doSomething('param1')->thenThrow(new InvalidArgumentException());

Mocks

Mocks в PHPUnit требует предоставления спецификации перед обращением к производственному коду:

$mock = $this->getMock('Namespace\CollaboratorClass');
$mock->expects($this->once())
  ->method('doSomething')
  ->with('param');
// act phase

Моки в Факе можно указать после взаимодействия:

$mock = Phake::mock('Namespace\CollaboratorClass');
// act phase
Phake::verify($mock)->doSomething('param');

Преимущество отдельных этапов проверки заключается не только в том, что отмечает Бенджамин: последовательная модель тестирования на основе состояния и поведения, где проверки заменяют ваши утверждения. Проверка в тесте означает, что возникают исключения, и вам не нужно проходить через ваш код, чтобы быть увиденным: на функциональном уровне и уровне принятия вы избегаете исключений фреймворка, которые будут пойманы в ваших предложениях catch ().

Никакие взаимодействия с макетом тоже не могут быть проверены:

$mock = $this->getMock('Namespace\CollaboratorClass');
$mock->expects($this->never())
  ->method('doSomething');
// act phase

С Phake:

$mock = Phake::mock('Namespace\CollaboratorClass');
// act phase
Phake::verifyNoInteractions($mock);

И многократные взаимодействия — последний интересный случай:

$mock = $this->getMock('Namespace\CollaboratorClass');
$mock->expects($this->at(0))
  ->method('doSomething')
  ->with('param1');
$mock->expects($this->at(1))
  ->method('doSomething')
  ->with('param2');
// act phase

С Phake:

$mock = Phake::mock('Namespace\CollaboratorClass');
// act phase
Phake::verify($mock)->doSomething('param1')
Phake::verify($mock)->doSomething('param2');

Phake изначально поддерживает сопоставления PHPUnit, поэтому вам не нужно менять их, превращая ваши ожидания в проверки:

$mock = $this->getMock('Namespace\CollaboratorClass');
$mock->expects($this->once())
  ->method('doSomething')
  ->with($this->isInstanceOf('Iterator');
// act phase

С Phake:

$mock = Phake::mock('Namespace\CollaboratorClass');
// act phase
Phake::verify($mock)->doSomething($this->isInstanceOf('Iterator'));

Шпионы

Когда ожидания сложны, и вы не готовы создать объект Constraint, Spies обеспечивают захват входного ввода в макет и позволяют вам работать с этой переменной, чтобы утверждать, что вы хотите, концептуально помещая ваши утверждения в макет.

$mock = $this->getMock('Namespace\CollaboratorClass');
$self = $this;
$mock->expects($this->once())
  ->method('doSomething')
  ->will($this->returnCallback(function($param) use ($self) {
  $self->param = $param;
  }));
// act phase
$self->assertEquals('42', $self->param->answer());

С помощью Phake вы также можете захватить вывод, но после фазы действия, в соответствии с другими проверками:

$mock = Phake::mock('Namespace\CollaboratorClass');
// act phase
Phake::verify($mock)->doSomething(Phake::capture($param));
$this->assertEquals('42', $param->answer());

Шпионы не должны использоваться часто — когда эти проверки повторяются между тестами, вы должны извлечь объект ограничения.

Выводы

Я полностью оправдал все ожидания, которые я бы писал каждый день с помощью PHPUnit или Phake. Phake — это мощный инструмент, который позволяет вам легко и правильно писать и читать ожидания на правильном этапе тестирования; однако, не будьте ослеплены этим инструментом и всегда упрощайте интерфейсы, для которых вы собираетесь ввести Test Doubles, в духе принципа разделения интерфейсов. Также запишите эти протоколы и ожидания с точки зрения класса клиента, опять же в духе другого принципа, инверсии зависимости, который ведет к внешнему типу TDD, предложенному ГСНО .