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