Недавно я взял на себя проект PHP, который содержит мало или совсем не тестирует. Этот проект довольно большой, классы в основном довольно маленькие, но есть одна большая проблема.
В защищенной или закрытой переменной есть скрытый локатор службы DI
поэтому программисты думают, что они поступают правильно, действуя как одиночка и передаваемые практически каждому классу. Класс, который в свою очередь использует его для получения зависимостей.
В прошлый четверг я создал новую команду из 3 человек, чья новая обязанность — сосредоточиться исключительно на тестировании и его автоматизации, и сегодня, наконец, один из них пришел ко мне с вопросом, которого я боялся. Вопрос, на который я не знаю ответа.
Дэвид, как я могу посмеяться над результатом метода, который
скрыто глубоко внутриDI
?
Переписывать модули для следования DI недопустимо, у нас нет ни бюджета, ни времени для этого.
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
результат? Переменные доступны через магию __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 сам. Есть ли обходной путь, который я еще не нашел?
Как я уже упоминал в моих комментариях, вы мог заглушить контейнер, который возвращает себя всякий раз, когда __get
Метод вызывается и возвращает макет объекта из фактического метода, который вы вызываете в конце. использованияКонтактная codeception-х Stub
составная часть, это может выглядеть примерно так:
$container = Stub::make(
'Your\Container',
[
'getUserBar' => $returnMock,
]
);
Stub::update($container, ['__get' => $container]);//return itself on __get access
Но, как вы говорите: это действительно указывает на более фундаментальную проблему с кодом, который вы пытаетесь протестировать.
Я нашел решение для 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
переменная вместо
Используя обратные вызовы, вы можете делать что угодно, даже имитировать возвращаемые значения через ссылочные параметры.