Модульное тестирование phpspec — использование реестра ioc/service для доставки конкретного класса для тестирования

Я новичок в тестировании, и я не уверен, что делаю это правильно:

Я хочу не выполнять модульный тест для определенного класса, а для любого класса, который разрешается из моего контейнера ioc. В контейнере ioc я привязываю свои интерфейсы к конкретным классам, например:

Пример (я использую Laravel 5):

// in a service provider
public function register(){ 

    $this->app->bind('FooInterface', function() { 
        return new SomeConcreteFoo; 
    }); 
 }

Затем я хочу написать модульный тест для FooInterface, а не SomeConcreteFoo, который позже можно будет заменить другим классом.

Причина, по которой я хочу это сделать, заключается в том, что мне кажется, что соответствующее тестирование должно быть нацелено на то, что возвращает мой контейнер ioc, поскольку именно это я буду использовать в приложении. Мне также кажется, что тестирование должно проводиться на уровне интерфейса, поскольку именно здесь я определяю ожидания, которые остальная часть приложения будет иметь по отношению к моему классу.

Мне трудно найти какую-либо информацию о том, как это сделать, что говорит мне о том, что я могу думать об этом неправильно. Например, возможно, то, что я пытаюсь выполнить, больше похоже на интеграционный тест, а не на модульный тест.

Итак, первый вопрос: правильно ли я думаю о тестировании? если нет, есть ли у вас какие-либо предложения относительно передового опыта для альтернативного пути тестирования.

Второй вопрос: если я правильно думаю, как мне настроить phpspec для использования контейнера IOC Laravel, чтобы я мог протестировать все, что возвращает IOC.


person andy-bc    schedule 01.02.2015    source источник


Ответы (1)


Я хочу не выполнять модульный тест для определенного класса, а для любого класса, который разрешается из моего контейнера ioc. В контейнере ioc я привязываю свои интерфейсы к конкретным классам, например [...]

Модульные тесты пишутся не так. Модульное тестирование заключается в изолированном описании поведения класса, поэтому единственным реально созданным объектом является тестируемый класс (а иногда и простые объекты-значения).

Затем я хочу написать модульный тест для FooInterface, а не для SomeConcreteFoo, который позже можно было бы заменить каким-либо другим классом.

Именно так вы должны писать свои модульные тесты. Предпочитайте интерфейсы для сотрудников.

Каждый макетный фреймворк поддерживает эту функцию и будет создавать для вас тестовые двойники, не заставляя вас предоставлять конкретные реализации.

class BarSpec extends ObjectBehavior
{
    function it_does_amazing_things(FooInterface $foo)
    {
        $results = ['a', 'b', 'c'];

        $foo->find('something')->willReturn($results);

        $this->findMeSometing()->shouldReturn($results);
    }
}

В этом конкретном примере PhpSpec будет использовать Prophecy (его фиктивный фреймворк) для создания тестового двойника FooInterface и внедрит его в метод примера. То, что вы делаете с этим объектом, определяет, является ли он подделкой, заглушкой или макетом.

Причина, по которой я хочу это сделать, заключается в том, что мне кажется, что соответствующее тестирование должно быть нацелено на то, что возвращает мой контейнер ioc, поскольку именно это я буду использовать в приложении.

Как объяснялось выше, модульные тесты фокусируются на поведении одного класса. Его сотрудники обычно фальшивые. Это по нескольким причинам. Один из них — скорость. Еще один — обратная связь. Если тест не пройден, мы получим четкую обратную связь о том, какой класс не работает. Если бы вы создавали всех соавторов, а не использовали двойников тестов, одна ошибка могла бы сделать весь ваш набор тестов красным. Я даже не буду упоминать, как сложно было бы поддерживать и создавать все необходимые объекты (хотя здесь могли бы помочь контейнеры).

Помните, что написание модульных тестов — это больше деятельность по проектированию, а не по тестированию.

Например, возможно, то, что я пытаюсь выполнить, больше похоже на интеграционный тест, а не на модульный тест.

Конечно. Подробнее о тестовой пирамиде. Большинство ваших тестов должны быть модульными тестами. Затем у вас должно быть некоторое количество интеграционных и приемочных тестов, которые будут проверять более одного класса одновременно. Причина, по которой вам нужно больше модульных тестов, чем интеграционных, заключается в том, что последние более хрупкие, и их сложнее поддерживать/изменять.

Используйте PHPUnit для интеграционных тестов. PhpSpec не подходит для этой работы. PhpSpec отлично подходит для разработки ваших классов (написание модульных тестов), особенно если вы делаете это сначала с тестами.

Второй вопрос: если я правильно думаю, как мне настроить phpspec для использования контейнера IOC Laravel, чтобы я мог протестировать все, что возвращает IOC.

Вы не знаете. Однако вы можете подумать об использовании контейнера в интеграционных тестах.

Немного чтения:

person Jakub Zalas    schedule 02.02.2015
comment
Не могли бы вы подробнее рассказать об интерфейсе? Скажем, у меня есть интерфейс interface FooInterface {...} и две конкретные реализации class FooA {...} и class FooB {...}. Могу ли я написать тест на общем уровне FooInterface {...}, который можно было бы использовать для обоих классов? Поскольку FooA и FooB имеют один и тот же интерфейс и общедоступное поведение, не кажется СУХИМ писать два отдельных теста для каждого класса. Любые намеки на то, как это сделать, или я все еще решаю это неправильно? - person andy-bc; 08.02.2015
comment
Я не согласен с одной небольшой частью — PhpSpec не подходит для интеграционных тестов. Я не нашел доказательств этому. Вы можете внедрить реальных соавторов (используя beConstructedWith) так же легко, как и двойников. - person Greg Bell; 01.01.2019
comment
@GregBell позвольте мне перефразировать - phpspec не был разработан с учетом интеграционных тестов. - person Jakub Zalas; 02.01.2019