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());
  }
}