Непрерывная интеграция с использованием 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

Обратите внимание на несколько ухищрений:

  1. Для ускорения следующих запусков закэширована директория с кэшом композера
  2. /bin директория нестандартная, потому что здесь тестируется не проект, а standalone бандл, для старта которого сделано приложение
  3. Использована механика для проверки доступности mysql с php контейнера. Проблема в том, что mysql стает доступным для PDO намного позже, чем начинает пинговаться с помощью mysqladmin ping. Если кто-то разберется с этим и напишет причину в комментариях - буду искренне признателен. =)

Ресурсы:

  1. https://github.com/FriendsOfPHP/PHP-CS-Fixer
  2. https://github.com/Behat/Behat
  3. https://travis-ci.org/
  4. https://github.com/harentius/docker-blog-bundle-base
  5. https://github.com/harentius/blog-bundle