Бенджамин Эберлей познакомил меня с 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, предложенному ГСНО .