Интеграционные тесты насмешливых фасадов и инъекций

У нас есть некоторые старые проекты laravel, которые используют фасады в классах.

use Cache;

LegacyClass
{
    public function cacheFunctionOne()
    {
         $result = Cache::someFunction('parameter');

         // logic to manipulate result

         return $result;
    }

    public function cacheFunctionTwo()
    {
         $result = Cache::someFunction('parameter');

         // different logic to manipulate result

         return $result;
    }
}

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

use Illuminate\Cache\Repository as Cache;

ModernClass
{
    public function cacheFunctionOne(Cache $cache)
    {
         $result = $cache->someFunction('parameter');

         // logic to manipulate result

         return $result;
    }

    public function cacheFunctionTwo(Cache $cache)
    {
         $result = $cache->someFunction('parameter');

         // different logic to manipulate result

         return $result;
    }
}

Я знаю, что фасады могут быть издевались

public function testExample()
{
    Cache::shouldReceive('get')
                ->once()
                ->with('key')
                ->andReturn('value');

    $this->visit('/users')->see('value');
}

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

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

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

$modernClass->cacheFunctionOne($cache); // easily mocked

// logic that changes data returned by laravel Cache object function 'someFunction'

$modernClass->cacheFunctionTwo($cache); // easily mocked with a different mock

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

$legacyClass->cacheFunctionOne();

// logic that changes data returned by laravel Cache object function 'someFunction'

$legacyClass->cacheFunctionTwo();

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

Я правильно понял это?

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

Ответы

Ответ 1

Инъекция зависимостей против фасадов

Одним из основных преимуществ Injection Dependency является то, что код становится намного более проверяемым, как только вы начинаете вводить зависимости в методы вместо того, чтобы создавать/кодировать их внутри метода. Это связано с тем, что вы можете проходить в зависимостях изнутри модульных тестов, и они будут распространяться через код.

Смотрите: http://slashnode.com/dependency-injection/

Инъекция зависимостей резко контрастирует с фасадами. Фасады представляют собой статические глобальные классы, язык PHP не позволяет перезаписывать или заменять статические функции на статические классы. Фасад Laravel использует Mockery для обеспечения макетной функциональности, и они ограничены теми же фактами, что и выше.

Проблема для тестирования интеграции может возникнуть там, где вы надеетесь извлечь данные из неизмеримого кеша, но как только вы используете Facade:: shouldReceive(), то Facade:: get() будет переопределяться издеваемым кешем. Обратное также верно. В результате, Фасады не подходят, если вы чередуете вызовы для издевающихся и незафиксированных данных.

Чтобы проверить свой код с различными наборами данных, которые вам нужны, наилучшей практикой было бы реорганизовать ваш устаревший код на использование DI.

Интеграционные тесты

Простой метод

Альтернативой является вызов нескольких Facade:: shouldReceive() с ожиданиями в начале вашего теста интеграции. Обеспечение того, чтобы у вас было правильное количество ожиданий в правильном порядке для каждого из вызовов, которые вы будете делать в тесте интеграции. Вероятно, это был бы более быстрый способ записи тестов с учетом существующей кодовой базы.

Более жесткий метод

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

Приложение: