PHPUnit: макет методов существующего объекта
PHPUnit getMock($classname, $mockmethods)
создает новый объект на основе данного имени класса и позволяет мне изменять/проверять поведение указанных мной методов.
Я стремлюсь к чему-то другому; это изменение поведения методов существующего объекта - без создания нового объекта.
Это возможно? Если да, то как?
Когда я размышлял над проблемой, я пришел к выводу, что это будет возможно, сериализуя объект, изменяя сериализованную строку, чтобы позволить экземпляру экземпляра нового класса, который расширяет старый класс, а также издевательские методы.
Мне нужен код для этого - или, может быть, есть такой код уже где-то.
В то время как было бы возможно создать вновь созданный объект, он слишком сложный, чтобы сделать это в моем тесте. Таким образом, я не хочу этого делать, если на самом деле я действительно этого не делаю. Это экземпляр TYPO3 TSFE, и установка этого параметра в процессе начальной загрузки уже достаточно сложна.
Ответы
Ответ 1
Позвольте мне начать, сказав: Добро пожаловать на темную сторону модульного тестирования.
Значение: вы обычно не хотите этого делать, но, как вы объяснили, у вас есть то, что кажется действительным прецедентом.
runkit
Что вы можете сделать довольно легко, но не тривиально, но проще, чем изменять свою архитектуру приложения, - это изменить поведение класса на лету, используя runkit
runkit_method_rename(
get_class($object), 'methodToMock', 'methodToMock_old'
);
runkit_method_add(
get_class($object), 'methodToMock', '$param1, $param2', 'return 7;'
);
runkit:: method_add
и после теста на метод_remove и переименовать снова. Я не знаю ни одного фреймворка/компонента, который поможет вам в этом, но не так много реализовать самостоятельно в UglyTestsBaseTest extends PHPUnit_Framework_TestCase
.
Ну...
Если все, к чему у вас есть доступ, является ссылкой на этот объект (как в: $x
в $x = new Foo();
), я не знаю, как сказать: $x, теперь вы имеете тип SomethingElse
и все другие переменные, указывающие на этот объект, тоже должны измениться.
Я собираюсь предположить, что вы уже знаете такие вещи, как тестирование ваших приватов, но это не поможет вам, потому что у вас нет контроль над жизненный цикл объектов.
Расширение php test helpers
Примечание: расширение Test-Helper заменяется https://github.com/krakjoe/uopz
Что также может помочь вам в следующем: Stubbing Hard-Coded Dependencies с помощью расширение php-test-helpers, которое позволяет вам делать такие вещи, как Перехват создания объектов.
Это означает, что пока ваше приложение вызывает $x = new Something();
, вы можете взломать PHP так, чтобы $x
затем содержал экземпляр YourSpecialCraftedSomething
.
Вы можете создать этот класс, используя PHPUnit Mocking API, или написать его самостоятельно.
Насколько я знаю, это ваши варианты. Если это стоит туда (или просто писать тесты интеграции/селена для этого проекта), вам нужно самостоятельно разобраться, так как это сильно зависит от ваших обстоятельств.
Ответ 2
Я знаю, что этот ответ довольно поздний, но я думаю, что для будущих зрителей этого вопроса в настоящее время существует более простое решение (имеющее некоторые недостатки, но в зависимости от ваших потребностей может быть намного проще реализовать). Mockery поддерживает насмешку над уже существующими объектами с тем, что они называют проксированным частичным макетом. Они говорят, что это для классов с конечными методами, но в этом случае они могут быть использованы (хотя в документах есть предупреждение, что это должно быть" последним средством").
$existingObjectMock = \Mockery::mock($existingObject);
$existingObjectMock->shouldReceive('someAction')->andReturn('foobar');
Он действует путем создания прокси-объекта, который передает все вызовы методов, и атрибут получает/устанавливает существующий объект, если они не издеваются.
Следует отметить, однако, что прокси-сервер страдает от очевидной проблемы с отказом каких-либо typechecks или typehints. Но этого обычно можно избежать, потому что $existingObject
все равно можно обойти. Вы должны использовать только $existingObjectMock
, когда вам нужны макетные возможности.
Ответ 3
Не все предварительно существующие коды могут быть протестированы. Код действительно должен быть разработан для проверки. Таким образом, хотя не совсем то, о чем вы просите, вы можете реорганизовать код таким образом, чтобы экземпляр объекта был в отдельном методе, а затем издевался над этим методом, чтобы вернуть то, что вы хотите.
class foo {
function new_bar($arg) { return new bar($arg); }
function blah() {
...
$obj = $this->new_bar($arg);
return $obj->twiddle();
}
}
а затем вы можете проверить его с помощью
class foo_Test extends PHPUnit_Framework_TestCase {
function test_blah() {
$sut = $this->getMock('foo', array('new_bar', 'twiddle'));
$sut->expects($this->once())->method('new_bar')->will($this->returnSelf());
$sut->expects($this->once())->method('twiddle')->will($this->returnValue('yes'));
$this->assertEquals('yes', $sut->blah());
}
}