Эквивалент SimpleTest "частичные mocks" в PHPUnit?
Я пытаюсь перенести кучу тестов из SimpleTest в PHPUnit, и мне было интересно, есть ли эквивалент для SimpleTest частичных mocks.
Обновление: я не могу найти ничего в документах, которые предполагают, что эта функция доступна, но мне пришло в голову, что я могу просто использовать подкласс. Это хорошая или плохая идея?
class StuffDoer {
protected function doesLongRunningThing() {
sleep(10);
return "stuff";
}
public function doStuff() {
return $this->doesLongRunningThing();
}
}
class StuffDoerTest {
protected function doesLongRunningThing() {
return "test stuff";
}
}
class StuffDoerTestCase extends PHPUnit_Framework_TestCase {
public function testStuffDoer() {
$sd = new StuffDoerTest();
$result = $sd->doStuff();
$this->assertEquals($result, "test stuff");
}
}
Ответы
Ответ 1
Из чтения связанной страницы частичный макет SimpleTest кажется макетом, где только некоторые из методов переопределены. Если это правильно, эта функциональность обрабатывается обычным PHPUnit mock.
Внутри PHPUnit_Framework_TestCase
вы создаете макет с
$mock = $this->getMock('Class_To_Mock');
Создает экземпляр mock, где все методы ничего не делают и возвращают null. Если вы хотите только переопределить некоторые из методов, второй параметр getMock
- это массив методов для переопределения.
$mock = $this->getMock('Class_To_Mock', array('insert', 'update'));
создаст mock-экземпляр Class_To_Mock
с удаленными функциями insert
и update
, готовыми для их возвращаемых значений.
Эта информация находится в phpunit docs.
Примечание, этот ответ показывает более современные примеры кода, для версий PHPUnit начиная с 5.4
Ответ 2
PHPUnit_Framework_TestCase::getMock
устарел с phpunit 5.4. Вместо этого мы можем использовать setMethods
.
setMethods (методы массива $) можно вызвать в объекте Mock Builder, чтобы указать методы, которые должны быть заменены с помощью настраиваемого тестового двойника. Поведение других методов не изменяется. Если вы вызываете setMethods (null), то никакие методы не будут заменены.
https://phpunit.de/manual/current/en/test-doubles.html
$observer = $this->getMockBuilder(Observer::class)
->setMethods(['update'])
->getMock();
Обратите внимание, что приведенный выше getMock
равен PHPUnit_Framework_MockObject_MockBuilder::getMock
. (Phpunit5.6)
Ответ 3
Я не думаю, что PHPUnit поддерживает частичные макеты для тестируемой системы. Если вы пытаетесь изолировать методы, я уверен, что ваша реализация работает - я тоже это сделал.
Однако я стараюсь избегать этого, по нескольким причинам.
Во-первых, он очень сильно сочетает ваш тест с внутренней реализацией класса. Вас действительно волнует, был ли вызван метод под названием doesLongRunningThing
, или это более важно, чтобы "LongRunningThing" было сделано?
Во-вторых, когда я сталкиваюсь с этим, это всегда заставляет задуматься, есть ли у меня один класс, выполняющий работу двух. Рефакторинг класса экстрактов может быть в порядке. Тестирование становится намного проще, если doesLongRunningThing()
становится его собственным классом даже с помощью одного метода.
Я считаю, что решение заключается в том, чтобы внедрять службы, на которые зависит SUT (http://en.wikipedia.org/wiki/Dependency_injection). Это также делает реализацию doesLongRunningThing
более надежной.
Без перехода в интерфейсы, вот что я сделал бы:
class DoesLongRunningThing {
public function execute() {
sleep(10);
return "stuff";
}
}
class StuffDoer {
protected $doesLongRunningThing;
public function setLongRunningThinger(DoesLongRunningThing $obj) {
$this->doesLongRunningThing = $obj;
}
public function doStuff() {
return $this->doesLongRunningThing->execute();
}
}
Теперь легко насмехаться:
class StuffDoerTestCase extends PHPUnit_Framework_TestCase {
public function testStuffDoer() {
$dlrtMock = $this->getMock('DoesLongRunningThing');
$dlrtMock->expects($this->any())->will($this->returnValue("test stuff"));
$sd = new StuffDoer();
$sd->setLongRunningThinger($dlrtMock);
$result = $sd->doStuff();
$this->assertEquals($result, "test stuff");
}
}