Результат тестирования и макета вложенного контейнера

1

Недавно я взял на себя проект 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. Есть ли обходной путь, который я еще не нашел?

  • 0
    Вставьте в тестовый экземпляр фиктивного DI и подключите его для возврата фиктивного объекта при вызове __get со значением Foo . Этот объект вы можете прекрасно контролировать. Также: взгляните на кодовое восприятие . Он находится поверх PHPUnit и значительно упрощает насмешку. Также: скажите парню, который задал этот вопрос, что, если тестирование сложнее, чем написание кода, это обычно означает, что код написан плохо
  • 0
    @EliasVanOotegem Спасибо за комментарий. Проблема, или, по крайней мере, я так думаю, заключается в том, что Foo возвращает контейнер, в котором, помимо прочего, хранится контейнер Bar . Я знаю, что могу просто начать издеваться снизу (результат метода getUseBar ) до главного входа DI , но это кажется неправильным, и поэтому я не хочу этого делать. Разве нет другого пути?
Показать ещё 3 комментария
Теги:
unit-testing
service-locator
phpunit

2 ответа

0
Лучший ответ

Я нашел решение для 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.

Вы можете делать что угодно, используя обратные вызовы, даже имитируя возвращаемые значения через контрольные параметры.

  • 0
    Похоже, хороший подход, кстати: если вы обнаружите, что написание всего этого кода просто для запуска тестов немного утомительно (я знаю, я бы это сделал), взгляните на компонент Stub кодового восприятия . Он находится поверх макета конструктора PHPUnit, но предлагает более лаконичный, интуитивно понятный API
  • 0
    @EliasVanOotegem Я также взглянул на Mockery, который, кажется, делает то же самое, но в итоге использовал доступные по умолчанию методы PhpUnit. В любом случае, еще раз спасибо за то, что указали мне правильное направление.
Показать ещё 1 комментарий
1

Как я уже упоминал в своих комментариях, вы можете заглушить контейнер, который возвращается сам при __get метода __get и возвращает издевательский объект из фактического метода, который вы вызываете в конце. использование компонента Stub создания кода, это может выглядеть примерно так:

$container = Stub::make(
    'Your\Container',
    [
        'getUserBar' => $returnMock,
    ]
);
Stub::update($container, ['__get' => $container]);//return itself on __get access

Но, как вы говорите: это действительно свидетельствует о более фундаментальной проблеме с кодом, который вы пытаетесь проверить.

  • 0
    Я принимаю этот ответ, потому что меня не волнует, работает ли этот конкретный код или нет, а потому, что мне нравится подход к решению этой проблемы, с которым я столкнулся, подход, который может быть воспроизведен большинством сред тестирования. Спасибо, Элиас, еще раз.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню