Недавно я взял на себя проект PHP, который практически не содержит тестов. Этот проект довольно большой, классы в основном довольно маленькие, но есть одна большая проблема.
Существует локатор сервисов, умственно скрытый внутри защищенной или частной переменной DI
, поэтому программисты считают, что они делают правильные вещи, которые действуют как одноэлементные и передаются в значительной степени для каждого отдельного класса. Класс, который в свою очередь использует его для извлечения зависимостей.
В прошлый четверг я создал новую команду из 3 человек, чья новая ответственность заключается в том, чтобы сосредоточиться исключительно на тестировании и ее автоматизации, и сегодня, наконец, один из них пришел ко мне с вопросом, которого я боялся. Вопрос, я не знаю ответа.
Дэвид, как я предполагаю, чтобы издеваться над результатом метода, который глубоко скрыт внутри
DI
?
Переписывание модулей, чтобы следовать за DI, неприемлемо, у нас нет ни бюджета, ни времени для этого.
Можно do()
вызвать метод do()
так?
class Baz extends AbstractBaz
{
public function foo()
{
$userProcess = $this->DI->Foo->Bar->FooBar->BarFoo->getUserBar();
$users = $userProcess->do();
// work with the $users variable
}
}
Сам локатор огромен, вы можете называть сотни методов, погружаясь глубже и глубже в него.
Есть ли способ быстро Foo->Bar->FooBar->BarFoo->getUserBar
результат Foo->Bar->FooBar->BarFoo->getUserBar
? Переменные доступны через магический метод __get
и намекают на аннотацию @property
.
Используя PHPUnit, было бы неплохо иметь что-то вроде этого:
$locator = $this
->getMockBuilder('\App\DI\Abstracted\DI')
->setMethods(['Foo->Bar->FooBar->BarFoo->getUserBar'])
->getMock();
$locator
->expects($this->any())
->method('Foo->Bar->FooBar->BarFoo->getUserBar')
->will($this->returnValue($desiredObject));
К сожалению, это не работает. Я не очень разбираюсь в PHPUnit. Есть ли обходной путь, который я еще не нашел?
Я нашел решение для PHPUnit раньше, но только у меня было время ответить.
Имитировать то, что я хотел, на самом деле возможно в PHPUnit, используя PHP Closure
, что и вызывает анонимные функции в PHP.
Ниже приведен небольшой пример PHPUnit
$classA = $this
->getMockBuilder('First\Class\To\Be\Mocked')
->disableOriginalConstructor()
->setMethods([
'The',
'names',
'of',
'desired',
'methods',
])
->getMock();
$classB = $this
->getMockBuilder('Second\Class\To\Be\Mocked')
->disableOriginalConstructor()
->setMethods([
'The',
'names',
'of',
'desired',
'methods',
])
->getMock();
$serviceLocator = $this
->getMockBuilder('Second\Class\To\Be\Mocked')
->disableOriginalConstructor()
->setMethods([
'__get',
'SomeMethod',
])
->getMock();
$serviceLocator
->expects($this->any())
->method('__get')
->will($this->returnCallback(function($name) use ($serviceLocator, $classA, $classB)
{
switch ($name)
{
case 'ClassA':
return $classA;
case 'ClassB':
return $classB;
default:
return $serviceLocator;
}
}));
Этот код при выполнении даст желаемые результаты.
$serviceLocator->This->That->Them->ClassA
вернет определенную переменную $classA
, изменив ClassA на ClassB, вместо этого локатор сервиса $classB
переменную $classB
.
Вы можете делать что угодно, используя обратные вызовы, даже имитируя возвращаемые значения через контрольные параметры.
Stub
кодового восприятия . Он находится поверх макета конструктора PHPUnit, но предлагает более лаконичный, интуитивно понятный API
Как я уже упоминал в своих комментариях, вы можете заглушить контейнер, который возвращается сам при __get
метода __get
и возвращает издевательский объект из фактического метода, который вы вызываете в конце. использование компонента Stub
создания кода, это может выглядеть примерно так:
$container = Stub::make(
'Your\Container',
[
'getUserBar' => $returnMock,
]
);
Stub::update($container, ['__get' => $container]);//return itself on __get access
Но, как вы говорите: это действительно свидетельствует о более фундаментальной проблеме с кодом, который вы пытаетесь проверить.
__get
со значениемFoo
. Этот объект вы можете прекрасно контролировать. Также: взгляните на кодовое восприятие . Он находится поверх PHPUnit и значительно упрощает насмешку. Также: скажите парню, который задал этот вопрос, что, если тестирование сложнее, чем написание кода, это обычно означает, что код написан плохоFoo
возвращает контейнер, в котором, помимо прочего, хранится контейнерBar
. Я знаю, что могу просто начать издеваться снизу (результат методаgetUseBar
) до главного входаDI
, но это кажется неправильным, и поэтому я не хочу этого делать. Разве нет другого пути?