Поговорим о том, как прекратить копипастить между проектами и вынести код в переиспользуемый подключаемый бандл Symfony 5. Серия статей, обобщающих мой опыт работы с бандлами, проведет на практике от создания минимального бандла и рефакторинга демо-приложения, до тестов и релизного цикла бандла. В предыдущей части мы создали конфигурацию бандла. В этой статье разберем как тестировать бандл, напишем несколько тестов и создадим внутри бандла микроприложение для их запуска. Часть 1. Минимальный бандл
Часть 2. Выносим код и шаблоны в бандл
Часть 3. Интеграция бандла с хостом: шаблоны, стили, JS
Часть 4. Интерфейс для расширения бандла
Часть 5. Параметры и конфигурация
Часть 6. Тестирование, микроприложение внутри бандла
Часть 7. Релизный цикл, установка и обновление Если вы не последовательно выполняете туториал, то скачайте приложение из репозитория и переключитесь на ветку 5-configuration. Инструкции по установке и запуску проекта в файле README.md. Финальную версию кода для этой статьи вы найдете в ветке 6-testing. До рефакторинга уже было написано 2 теста: юнит тест одного из сервисов и функциональный тест контроллера. У нас примитивное приложение, в которой слой инфраструктуры (БД) не отделен от доменной логики: тесты используют временную базу данных sqlite. Проверим, работают ли тесты: В тестах остались названия пространств имен App\, оставшиеся от кода до его переноса в бандл. Исправим в обоих тестах пространства имен на bravik\CalendarBundle и запустим снова.
Тесты должны пройти успешно. В корне бандла создадим папку tests (bundles/CalendarBundle/tests) и перенесем туда папку tests/Service с юнит тестом. Функциональный тест в папке Controller перенесем чуть позже. Чтобы запустить тест, нам потребуется установить PHPUnit внутри бандла: Это добавит PHP Unit в качестве dev-зависимости в composer.json бандла, а так же создаст файл composer.lock. В бандлах он нам не нужен: создайте .gitignore файл и добавьте его туда. PHP Unit «из коробки» не заработает. Его нужно настроить: указать где лежат тесты и подключить автозагрузчик composer.  Скопируйте из приложения-хоста в бандл файл phpunit.xml.dist.  Благодаря тому, что структура папок бандла идентична обычному Symfony-приложению, поменять нужно всего 1 строку bootstrap: Здесь в аттрибуте bootstrap нам нужно указать путь к автолоадеру composer, чтобы PHPUnit мог воспользоваться стандартным механизмом автозагрузки классов. Запускаем тест в бандле: Тест должен успешно пройти!  Теперь попробуем протестировать контроллер: скопируйте тест из приложения-хоста в бандл. И… как теперь его запускать?
Ведь в бандле нет приложения. В первой статье про бандлы, мы описали 2 способа разработки бандла.
Временно, — для удобства, — мы разрабатывали бандл прямо внутри приложения-хоста.
Но пора оторваться от родительского дома и пустить наш бандл во взрослую жизнь. Чтобы хранить и запускать тесты бандла из самого бандла, и дальше его автономно разрабатывать,
создадим внутри него минималистичное микроприложение-хост. Главный компонент веб-приложения Symfony — его ядро Kernel из пакета symfony/http-kernel.
Ядро принимает запрос, передает его обработчику, и возвращает ответ. Можно подключить этот пакет отдельно. Но, с другой стороны, сложно представить себе приложение Symfony без DI-контейнера, роутинга и прочих плюшек. Поэтому сразу подключим набор пакетов symfony/framework-bundle, в который входит и http-kernel.  Создадим папку tests/App и класс TestingKernel внутри. Новый класс унаследуем от Symfony\Component\HttpKernel\Kernel и реализуем два абстрактных метода, которые требует от нас родитель: Чтобы пользоваться автозагрузчиком composer мы указали для ядра пространство имен bravik\CalendarBundle\Tests\App. На вход конструктора ядро принимает обязательные параметры: строковую константу 'test', обозначающую окружение и опцию включения отладочного режима. Здесь мы переопределяем конструктор, чтобы сразу зафиксировать нужные нам значения.  Метод registerBundles() возвращает массив с инстанциированными классами всех подключенных бандлов. Подключим наш: Метод registerContainerConfiguration() загружает конфиги и формирует DI-контейнер.
Пока оставим его пустым.  Чтобы протестировать контроллер нам потребуется компонент symfony/router. С его помощью, микроприложение должно научиться считывать аннотации в контроллерах и сопоставлять роуты экшнам. Давайте посмотрим как это делается в обычном приложении. Загляните в src/Kernel приложения-хоста: Мы видим, что обычное Symfony-приложение использует «допинг» в виде трейта MicroKernelTrait.  Внутри него уже реализован метод registerContainerConfiguration(), конфигурирующий контейнер
и добавлено два абстрактных метода-хука: Сделаем аналогично: Внутри метода configureRoutes() загрузим конфигурацию аннотаций из файла config/routes.yaml бандла: Так как наш конфиг в формате yaml, нам потребуется добавить компонент для его парсинга:  Теперь мы имеем минимальное ядро для запуска приложения. Но как использовать его в тестах? Вернемся в тест tests/Controller/EventControllerTest. Чтобы протестировать контроллер нам нужно отправить к нему HTTP запрос. По идее здесь нам потребуется браузер или другой HTTP-клиент. Однако фреймворк устроен таким образом, что настоящий браузер использовать не обязательно. В Symfony запросы браузера моделируются абстракцией Request, а потом передаются в ядро для обработки. Посмотрим на index.php: Аналогично этому мы можем сформировать фейковый запрос вручную, отдать его ядру и проверить возвращаемый им результат. Для упрощения работы с такими запросами, в Symfony есть класс HttpKernelBrowser и специальный пакет: В тесте это может выглядеть так: Здесь мы инициализируем ядро и передаем его в HttpKernelBrowser. После этого имитируем запросы к ядру через $client->request() и тестируем полученный результат. Вместо того, чтобы инициализировать ядро и клиент вручную, мы унаследуем наш тест от класса WebTestCase, предоставленному Symfony. В этом классе уже определен метод createClient(), создающий клиента, и метод createKernel(), создающий ядро.  Единственное, что нам потребуется сделать, — это указать какое именно ядро нужно использовать. Это делается установкой переменной окружения KERNEL_CLASS в phpunit.xml.dist: Теперь можно попытаться запустить наш тест: Упс... Ошибка пришла к нам из MicroKernelTrait. Этот класс добавляет в DI-контейнер немного конфигурации «по-умолчанию», в том числе для компонента framework. Но мы еще не добавили в ядро FrameworkBundle. Сделаем это: Запустим тест еще раз:  Что-тут происходит? При чем тут EditorController? Когда мы подключили new CalendarBundle() в TestingKernel, бандл подключил к сборке свой конфиг services.yaml, в котором у нас явно определены необходимые сервисы и их зависимости. Кроме этого мы пользуемся механизмом autowiring Symfony внутри самих этих сервисов. Мы указываем с помощью typehints нужные нам сервисы в конструкторе или экшнах контроллера, а Symfony при компиляции контейнера считывает эти typehints и автоматически добавляет их в конфигурацию зависимостей. Но чтобы они там оказались, нужно установить, сконфигурировать и загрузить все используемые нашим бандлом зависимости в ядро TestingKernel. Установим используемые в бандле зависимости: Подключим к ядру TestingKernel: Создадим конфигурационный файл: tests/App/config/config.yaml: В отличие от обычного приложения, где конфигурация разбивается по бандлам в папке packages, мы поместим все в общий конфигурационный файл. Здесь нам нужно определить обязательные параметры для Framework, Doctrine, нашего собственного бандла и зарегистрировать фикстуры. После этого подключим конфиг к ядру TestingKernel: Запускаем тест снова, и наконец-то успех!  Мы запустили наше микроприложение, контейнер скомпилировался, роутинг подключился и тест достучался до нашего контроллера. Мы разобрались как писать и запускать тесты непосредственно в бандле, а так же погрузились в устройство Symfony и создали микро-приложение для запуска тестов прямо внутри бандла. Финальную версию кода для этой статьи вы найдете в ветке 6-testing. В следующей статье поговорим о том как выпускать новые релизы бандла без боли и страданий, как организовать процесс первичной установки и обновления бандла. Часть 1. Минимальный бандл
Часть 2. Выносим код и шаблоны в бандл
Часть 3. Интеграция бандла с хостом: шаблоны, стили, JS
Часть 4. Интерфейс для расширения бандла
Часть 5. Параметры и конфигурация
Часть 6. Тестирование, микроприложение внутри бандла
Часть 7. Релизный цикл, установка и обновление Веб-разработчик PHP, Java