Запуск PHP Unit-тестов с code coverage в PhpStorm при работе в Docker

В PhpStorm есть возможность создавать Run/Debug Configurations и запускать их из IDE. Это позволяет участникам проекта делиться между собой общими конфигурациями, используя “Shared” флаг в настройках. Одной из таких операций является запуск юнит тестов во время разработки. Поговорим о том, как настроить этот процесс с использованием Docker.

Есть плагин PHPUnit code coverage для отображения покрытия кода тестами в PhpStorm. PhpUnit можно запустить с опциями для генерации отчета по code coverage. В этом отчете будет храниться информация о количестве вызовов каждого оператора во время выполнения тестов. По этой статистике можно вывести процент покрытия кода тестами, ведь если в файле есть операторы, которые ни разу не вызывались во время выполнения тестов, значит, этот код тестами не покрыт. Это и показывает плагин в виде таблицы с навигацией по директориям и файлам. Также плагин умеет подсвечивать зеленым и красным цветом строки в ваших файлах, которые, соответственно, покрыты или не покрыты тестами:

PHPUnit подсветка текста

Чтобы этот плагин работал корректно в PhpStorm с PHP CLI, настроенном из Docker-контейнера, необходим ряд дополнительных действий.

Во-первых, на данный момент в PhpStorm не реализована корректно работа с уже запущенными контейнерами. Это известный недостаток, обсуждается тут. Есть issue, в котором пользователи предлагают варианты решения, так же есть issue, в котором планируется фикс этой проблемы. Советую подписаться, чтобы быть в курсе. Если кратко, то проблема в том, что при настройке запуска Run Configuration из Docker, PhpStorm перезапускает контейнер с PHP каждый раз, когда выполняет в нем скрипт, после чего останавливает контейнер. Это можно увидеть в результатах выполнения запуска тестов из PhpStorm:

Testing started at 12:47 PM ...
docker-compose://[/home/user_name/sites/project_name/docker-compose.yml]:php_cli/php -dxdebug.coverage_enable=1 /app/vendor/phpunit/phpunit/phpunit --coverage-clover /opt/phpstorm-coverage/ProjectName$Run_all_tests.xml --configuration /app/phpunit.xml.dist --teamcity
Recreating project_name_php_cli_1 ...<----- тут пересоздается контейнер
Attaching to project_name_php_cli_1
PHPUnit 7.3.1 by Sebastian Bergmann and contributors.
 
Recreating project_name_php_cli_1 ... done
 
Time:7.9 seconds, Memory: 42.00MB
 
OK (689 tests,1663 assertions)
 
Generating code coverage report in Clover XML format ... done
project_name_php_cli_1 exited with code 0
Aborting on container exit...<----- тут контейнер удаляется
 
Process finished with exit code 0

Такой подход не дает возможности находиться в перманентной разработке. Когда вы пишете тесты и функционал в IDE, потом запускаете тесты, после чего переключаетесь в браузер и тестируете функционал вручную, вам необходимо каждый раз запускать PHP контейнер вручную, так как PhpStorm останавливает его после выполнения вашего консольного скрипта из Run/Debug меню. Для обхода этого побочного эффекта, до момента его исправления со стороны разработчиков JetBrains, комьюнити придумало самый простой вариант. Необходимо создать еще один PHP контейнер, который будет использоваться только для Run/Debug Configurations в PhpStorm. IDE будет его запускать, останавливать, делать с ним, что хочет. При этом ваш основной PHP контейнер не будет умирать, и вы сможете запускать свой код из веб-браузера (или другого клиента для работы с HTTP запросами). Конфигурация при этом выглядит следующим образом:

docker-compose.yml

version:'2'
services:
    php:
        image: jekakm/php71-core:201807161
        environment:-"PHP_IDE_CONFIG=serverName=project-name-docker"
        volumes:-"./docker-configs/php-fpm.ini:/etc/php/7.1/fpm/php.ini"-"./docker-configs/php-cli.ini:/etc/php/7.1/cli/php.ini"-".:/app:cached"
 
    # This container should be used for running php scripts inside PhpStorm.
# Because PhpStorm recreates the php container every time it runs a script.
# It is a temporary solution until it be fixed in new releases of PhpStorm.
    php_cli:
        build:
            context:./docker-configs/php-image
        environment:-"PHP_IDE_CONFIG=serverName=project-name-docker"
        volumes:-"./docker-configs/php-cli.ini:/etc/php/7.1/cli/php.ini"-".:/app:cached"

Примечание. Имейдж jekakm/php71-core:201807161 является нашим студийным имейджем для PHP контейнера. Вы можете взять за основу любой другой имейдж для PHP.

Контейнер php_cli должен наследоваться от контейнера php либо быть его копией. В зависимости от конкретного PHP-имейджа и конкретной платформы, для корректной работы code coverage плагина необходим root доступ внутри контейнера. Мы эту проблему решили путем дополнительной инструкции в нашем имейдже для консольного PHP. Если PhpStorm в отчете о запуске тестов с code coverage пишет, что не может получить доступ к определенной директории, тогда необходимо добавить в файл ./docker-configs/php-image/Dockerfile следующее:

./docker-configs/php-image/Dockerfile

FROM jekakm/php71-core:201807161
USER root

Теперь нужно настроить и выбрать PHP Server из Docker для наших скриптов из Run/Debug. Для этого в настройках шторма заходим в «Languages & Frameworks → PHP → CLI Interpreter» и добавляем новый сервер. Также отмечаем галочкой “Visible only for this project”, так как под каждый проект у вас должен быть свой docker-compose.yml, чтобы подключения не шарились между проектами в шторме. Выбираем конфигурацию для Docker Composer, далее из доступных сервисов в выпадающем меню Service выбираем тот, который мы создали — php_cli:

server

Также необходимо зайти в настройки “Languages & Frameworks → PHP → Test Frameworks” и создать новую конфигурацию для “PHPUnit by Remote Interpreter”. В выпадающем списке интерпретаторов выбираем тот, который мы настроили шагом выше — PHP 7.1 Docker Compose. У вас есть возможность сконфигурировать настройки PHPUnit по умолчанию. Так как тесты запускаются из Docker, нам необходимо указать пути к файлам относительно их расположения в контейнере. В нашем случае проект находится в папке /app, поэтому путь к автолоадеру указан: /app/vendor/autoload.php, а путь к конфигурационному файлу: /app/phpunit.xml.dist. Важно правильно указать этот путь, так как шторм подсказку вам не даст.

settings

Также важно отметить, что для того, чтобы шторм правильно обработал файл с результатом покрытия, ему нужно правильно указать путь к файлам. Для этого в настройках Test Frameworks для конфигурации, настроенной на работу с докером, необходимо добавить новое правило в Path mappings. Локальный путь должен указывать на директорию, в которой хранится исходный код у вас локально, в примере это /home/user/sites/project_name. Remote Path — это путь к корню вашего проекта в докер контейнере, в примере это /app:

path

Это необходимо сделать, так как в отчете о покрытии кода хранятся пути к файлам, которые обработал PHPUnit. Отчет, сгенерированный в Docker, соответственно, имеет пути к файлам относительно расположения в контейнере. И когда в шторме открыт файл, который есть в отчете, то шторм подсвечивает в нем покрытие строк. Если же локальный путь файла и путь файла из отчета не будут совпадать, то шторм просто не поймет, что путь из Docker-контейнера соответствует локальному пути, и файл нужно подсветить.

После всех настроек пересобираем контейнеры, запускаем их, работаем с проектом в браузере, параллельно запускаем тесты в PhpStorm, которые выполняются в контейнере php_cli и не трогают контейнер php. Использование отдельного контейнера для запуска консольных утилит не ограничено одним лишь PHPUnit. Теперь, имея настроенный интерпретатор из контейнера php_cli, вы можете создать другие Run/Debug Configurations в PhpStorm, например, любой PHP Script, который также будет запускаться в этом короткоживущем контейнере. Это позволяет видеть результат выполнения сразу в IDE без необходимости переключаться в терминал и набирать команду вручную. А также это позволяет оптимизировать разработку, так как на Run/Debug Configurations можно назначить горячие клавиши в PhpStorm — и запуск рутинных задач будет сводиться к конкретной комбинации клавиш на вашей клавиатуре.

Еще одним источником проблем в связке PhpStorm + Docker может выступать утилита docker-compose. Не забывайте ее обновлять. Сам столкнулся с тем, что PhpStorm вообще не хотел ее запускать, так как у меня была установлена слишком ранняя версия. Обновление к последней актуальной версии решило эту проблему. Проверить наличие актуальных версий можно тут, найти инструкции по обновлению docker-compose можно тут.

P.S. Если разработчики JetBrains со временем доработают поведение работы утилиты docker-compose в своих IDE (да, эта проблема актуальна не только для PhpStorm), то потребность в приведенном выше обходном способе отпадет.