Непрерывная интеграция с использованием Travis CI и Behat
Эта заметка начинает цикл заметок о процессах разработки и деплоя приложений. Для начала покажу, как с помощью сервиса Travis CI можно относительно легко реализовать процесс проверки качества и работоспособности кода.
Практику постоянной проверки сборки на наличие дефектов называют Непрерывная интеграция (Continuous Integration).
Вопрос написания тестов в этой статье не будет рассмотрен - это тема другой статьи. Сделано предположение, что у вас уже настроен Behat, и тесты уже могут быть локально запущены с использованием Selenium.
Для начала, проверим, нет ли в коде нарушений установленного код стайла. Это позволит ознакомиться и начать использовать Travis CI. Если у вас нету тестов - настоятельно рекомендую использовать как минимум проверку стиля кода.
Обоснование важности консистентности кода и и его стиля можно найти во множестве мест, например, Стив Макконнелл “Совершенный код” часть 7 раздел 31.
Для этого используем PHP-CS-Fixer.
В корень проекта в файл .php_cs можно положить настройки. Мой конфиг:
<?php
return PhpCsFixer\Config::create()
->setRiskyAllowed(true)
->setCacheFile(__DIR__ . '/.php_cs.cache')
->setRules([
'@Symfony' => true,
'@Symfony:risky' => true,
'@PHP56Migration' => true,
'combine_consecutive_unsets' => true,
'array_syntax' => ['syntax' => 'short'],
'no_useless_else' => true,
'no_useless_return' => true,
'ordered_class_elements' => true,
'ordered_imports' => true,
'concat_space' => ['spacing' => 'one'],
'psr4' => true,
'strict_comparison' => true,
'strict_param' => true,
'phpdoc_align' => false,
'phpdoc_order' => true,
'phpdoc_separation' => false,
'phpdoc_to_comment' => false,
'no_empty_phpdoc' => false,
'phpdoc_add_missing_param_annotation' => ['only_untyped' => false],
'modernize_types_casting' => true,
'no_php4_constructor' => true,
'php_unit_construct' => true,
'php_unit_strict' => true,
'semicolon_after_instruction' => true,
'doctrine_annotation_indentation' => true,
'doctrine_annotation_spaces' => ['around_array_assignments' => false],
'pre_increment' => false,
'yoda_style' => false,
])
;
Дальше добавляем в .travis.yml
language: php
php: '7.2'
jobs:
include:
- stage: codestyle-check
before_script:
- composer global require friendsofphp/php-cs-fixer
script:
- $HOME/.composer/vendor/bin/php-cs-fixer --diff --dry-run --verbose fix ./src
Я сразу выделил stage codestyle-check, так как позже мы добавим stage также для запуска тестов.
Пакет php-cs-fixer ставится отдельно, потому что для проверки стиля вендора не нужны - это ускоряет проверку.
Добавим еще один stage для запуска поведенческих тестов. Для этого нужно полностью рабочее приложение.
Используем для этого docker и docker-compose для упрощения управления взаимодействием контейнеров.
Для начала, нужно создать Dockerfile для приложения. Из-за специфики php из-за того, что php сам по себе не есть веб-сервером), я создал образ, который содержит php и nginx. Я сразу же оформил его в виде отдельного, базового образа, для того, чтобы в дальнейшем его можна было переиспользовать для прода. Для управления запущеными процессами использован supervisor. Ознакомится с образом можно здесь: https://github.com/harentius/docker-blog-bundle-base
На базе этого образа можно легко создать docker-compose.yml файл для запуска поведенческих тестов:
version: '3.4'
services:
blog_app:
image: harentius/blog-bundle-base
container_name: blogbundle_app
volumes:
- ./:/app
links:
- mysql
mysql:
image: mysql
container_name: blogbundle_mysql
selenium:
image: selenium/standalone-chrome
links:
- blog_app
Так как исходники уже и так склонированы в travis, директория с исходниками (и вендорами) просто монтируется (а не копируется) в контейнер
Дополняем .travis.yml стейджом с поведенческими тестами:
language: php
php: '7.2'
cache:
directories:
- $HOME/.cache/composer
jobs:
include:
- stage: codestyle-check
before_script:
- composer global require friendsofphp/php-cs-fixer
script:
- $HOME/.composer/vendor/bin/php-cs-fixer --diff --dry-run --verbose fix ./src
- stage: behavioral-tests
sudo: required
services:
- docker
before_script:
- mkdir ./tests/app/var/db-dumps
- composer install --no-interaction
- docker-compose -f support/ci/docker-compose.yml --project-directory=. up --build -d
# Even after mysql fully started (it can be pinged by 'mysqladmin ping'),
# there is still some time during which mysql for some reason is inaccessible by PDO.
# Following workaround fixes that
- |
while ! docker exec -it blogbundle_app php -r "new PDO('mysql:host=mysql', 'root', '1111');" ; do
sleep 1;
done
- docker exec -it blogbundle_app /app/tests/app/bin/install
script:
- docker exec -it blogbundle_app /app/vendor/bin/behat -c /app/behat.yml.dist
Обратите внимание на несколько ухищрений:
- Для ускорения следующих запусков закэширована директория с кэшом композера
- /bin директория нестандартная, потому что здесь тестируется не проект, а standalone бандл, для старта которого сделано приложение
- Использована механика для проверки доступности mysql с php контейнера. Проблема в том, что mysql стает доступным для PDO намного позже, чем начинает пинговаться с помощью mysqladmin ping. Если кто-то разберется с этим и напишет причину в комментариях - буду искренне признателен. =)