Непрерывная интеграция для PHP, используя TeamCity, Travis CI и Scrutinizer

Непрерывная интеграция (англ. Continuous Integration, CI) — это практика разработки программного обеспечения, которая заключается в выполнении частых автоматизированных сборок проекта для скорейшего выявления и решения интеграционных проблем. В обычном проекте, где над разными частями системы разработчики трудятся независимо, стадия интеграции является заключительной. Она может непредсказуемо задержать окончание работ. Переход к непрерывной интеграции позволяет снизить трудоёмкость интеграции и сделать её более предсказуемой за счет наиболее раннего обнаружения и устранения ошибок и противоречий.

Непрерывная интеграция является главным шагом на пути к непрерывному развертыванию ПО и одним из основных приёмов экстремального программирования.

В статье я опишу запуск тестов и проверку кода на различные стандарты. Но так же можно использовать тесты BDD, нагрузочные тесты, smoke тесты, тесты проверки безопасности (SQL инъекции, XSS, CSRF, clickjacking) и многие другие.

Установка сервера Continuous Integration

Средства непрерывной интеграции:

Свой выбор сделал в пользу TeamCity, т.к. эта система наиболее простая в установке и настройке.

В качестве сервера я использую Ubuntu и мои проекты написаны на языке PHP.

Поэтому на чистый сервер нужно установить необходимое ПО, например так:

$ sudo apt-get install acl curl mc git nginx php5-cli php5-common php5-mysql php-apc php5-gd php5-fpm php5-cgi php-pear php5-mcrypt php5-intl php5-xdebug php5-dev php5-curl php5 php5-dev php5-imagick php5-pspell php5-sqlite php5-xmlrpc php5-xsl php5-json libssh2-php redis-server openjdk-7-jdk sendmail npm nodejs supervisor ufw jpegoptim optipng graphviz

Далее необходимо убедиться, что установлен Composer - менеджер php пакетов.

После этого нужно установить инструменты тестирования качества кода.

Теперь можно приступить к установке TeamCity:

$ cd /usr
$ sudo wget https://download-ln.jetbrains.com/teamcity/TeamCity-9.0.3.tar.gz
$ sudo tar zxvf TeamCity-9.0.3.tar.gz
$ sudo rm TeamCity-9.0.3.tar.gz
$ sudo chown -R www-data:www-data /usr/TeamCity
$ sudo mkdir /opt/TeamCity
$ sudo chown -R www-data:www-data /opt/TeamCity

Здесь я скачал и распаковал последнюю на момент написания статьи версию TeamCity. Другие версии устанавливаются аналогично приведенным командам.

Далее выполнив команду, мы получим sh файл, который будем использовать для старта системы.

$ sudo nano /etc/init.d/teamcity

Содержимое файла:

#! /bin/sh
# /etc/init.d/teamcity -  startup script for teamcity
export TEAMCITY_DATA_PATH="/opt/TeamCity/.BuildServer"

# Carry out specific functions when asked to by the system
case "$1" in
    start)
        echo "Starting script teamcity"
        start-stop-daemon --start  -c www-data --exec /usr/TeamCity/bin/runAll.sh start
        ;;
    stop)
        echo "Stopping script teamcity"
        start-stop-daemon --start  -c www-data --exec /usr/TeamCity/bin/runAll.sh stop
        ;;
    *)
        echo "Usage: /etc/init.d/teamcity {start|stop}"
        exit 1
        ;;
esac
exit 0

И запустим сервис:

$ sudo chmod 0755 /etc/init.d/teamcity && sudo service teamcity start

CI сервер будет доступен по url: https://127.0.0.1:8111/

Для удобства можно создать proxy-server на 80 порту. Пример для nginx (не забудьте указать вместо 'ci.makedev.org' свой домен):

server {
    listen   80;
    server_name  ci.makedev.org;
    access_log off;
    location / {
        proxy_pass https://127.0.0.1:8111;
        proxy_set_header    Host            $host;
        proxy_set_header    X-Real-IP       $remote_addr;
        proxy_set_header    X-Forwarded-for $remote_addr;
        port_in_redirect off;
        proxy_connect_timeout 300;
    }
}

При этом все остальные порты в целях безопасности нужно закрыть (не забыть открыть 22 порт для ssh). Для этого, например в Ubuntu Server, можно использовать ufw:

$ sudo apt-get install ufw
$ sudo ufw enable
$ sudo ufw default deny
$ sudo ufw allow 8111
$ sudo ufw allow 80
$ sudo ufw allow 22
$ sudo ufw allow 80/tcp

Вот и все, сервер непрерывной интеграции установлен. Теперь осталось его настроить.

Настройка сервера Continuous Integration

Сразу предупреждаю, что описание максимально детализированное со множеством скриншотов. Главный принцип, который был использован при написании статьи, лучше один раз увидеть, чем сто раз услышать, или в нашем случае прочитать.

Всю настройку сервера CI можно сделать через веб админ панель (главная причина выбора TeamCity). Для этого нужно зайти по адресу https://127.0.0.1:8111/ или указать домен, который используется как proxy (например: https://ci.makedev.org/).

Если все сделали правильно, то мы увидим экран приветствия.

Экран приветствия

Далее нам нужно выбрать базу данных, в которой TeamCity будет хранить свои настройки. Я оставляю базу данных по умолчанию, т.к. не планирую использовать СУБД для своих проектов.

Выбор базы данных

После система создаст базу данных автоматически.

Создание базы данных

И проведет инициализацию компонентов.

Инициализация компонентов

Перед началом работы нам нужно принять лицензионное соглашение.

Лицензионное соглашение

После этого TeamCity предложить создать пользователя, который будет суперадминистратором.

Создание пользователя

Предварительная настройка системы закончена и мы видим профайл созданного пользователя. Здесь мы можем установить email, пароль и другие настройки.

Профайл пользователя

Вот мы на странице проектов, но пока здесь пусто.

Пустая страница проектов

Создадим новый проект. Главный проект будет называться My bundles, здесь я буду тестировать все свои Symfony2 бандлы.

Создание проекта

Так выглядит экран General Settings (Общие Настройки) нового пустого проекта.

General Settings (Общие Настройки)

Т.к. мы планируем использовать различные инструменты проверки качества кода, которые генерируют различные отчеты, нам нужно создать вкладки, в которых будут отображаться отчеты. Для перехода к окну редактирования вкладок отчетов нужно кликнуть Report Tabs:

Report Tabs (Вкладки очтетов)

Можно создать вкладки для проекта либо для сборки. Я использую вкладки для сборок, т.к. у меня каждая сборка представляет свой собственный бандл. Жмем Create new build report tab (Создать новую вкладку отчета сборки).

Create new build report tab (Создать новую вкладку отчета сборки)

Для каждого отчета нужна своя вкладка. Я буду использовать следующие вкладки для соответствующих инструментов:

Code Coverage              coverage.zip!index.html
CodeBrowser                cb.zip!/index.html
phpDocumentor              docs.zip!index.html
PHPLOC                     phploc.html
PHP Depend                 pdepend.html

Пример вкладок для отчетов

Здесь мы видим особый формат запуска - из архива. Так работает TeamCity. Где геренируются эти архивы я расскажу позже.

Сделаю небольшое отступление. Есть возможность настроить очистку файловой системы от старых сборок и артефактов. Иначе есть возможность заполнить жесткий диск сервера. Это можно сделать в настройках проекта на вкладке Clean-up Rules (Правила очистки).

Clean-up Rules (Правила очистки)

Edit позволит отредактировать правила для созданного проекта. Здесь я использую правило Everything для очистки всего (Clean everything including artifacts, history, and statistical data) и очищать все, что старше 10 последних успешных сборок.

Clean everything including artifacts, history, and statistical data

Еще один важный момент. Т.к. у нас доступ к репозиторию через ssh, нам нужно настроить ssh ключ, который необходим для доступа к коду в системе VCS (в моем случае это git, об этом далее). Жмем на вкладку SSH Keys (SSH ключи).

SSH Keys (SSH ключи)

Я создал свой ключ для доступа к github, инструкция описана в справке github. Нам нужен приватный ключ id_rsa, который хранится в папке .ssh. Нажимаем кнопку Upload SSH Key (Загрузка SSH ключа) и в диалоговом окне указываем путь к файлу.

Upload SSH Key (Загрузка SSH ключа)

После можно увидеть результат загрузки ключа.

Загруженный ключ для github

С настройками проекта закончили. Теперь можно создать конфигурацию сборки. Вернемся к окну General Settings (Общие Настройки).

General Settings (Общие Настройки)

Создать сборку можно, нажав кнопку Create build configuration (Создать конфигурацию сборки). Я буду создавать конфигурацию сборки для своего проекта GtmBundle (Google Tag Manager) для Symfony2.

Создание сборки для проекта GtmBundle (Google Tag Manager) для Symfony2

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

Настройка VCS (системы контроля версий)

Все свои проекты я храню на github, соответственно я использую систему контроля версий git.

Одно уточнение. Здесь я использую метод авторизации (Authentication method) - загруженный ключ (Uploaded Key), о котором речь шла ранее.

Настройка git

Теперь перед сохранением можно проверить подключение. Жмем Test connection (проверка подключения). Если все сделано правильно - увидим диалог с сообщением об успешном подключении.

Test connection (проверка подключения)

Сохраняем настройки и видим наше подключение во вкладке сборки Version Control Settings (настройки системы контроля). Здесь можно менять или добавлять систему контроля версий и ее настройки.

Version Control Settings (настройки системы контроля)

Не забудьте настроить очистку файлов перед началом сборки. Для этого кликните по ссылке Show advanced options (показать дополнительные параметры) и поставьте галочку в поле Clean build (очистить сборку).

Clean build (очистить сборку)

Здесь можно указать расположение проекта для сборки, указав Checkout directory (каталог для копирования проекта из VCS). Я использую Auto для выделения места внутри tmp, но если надо статический путь, например, если нужно открыть проект миру через nginx, его можно указать здесь, выбрав Custom path (пользовательский путь).

Настроим расписание запуска сборки. Оптимальный вариант проверять каждый коммит и ночью по расписанию проверять зависимости. Для настройки перейдем на вкладку Triggers (тригеры).

Triggers (тригеры)

Нажимаем Add new Trigger (добавить новый триггер). Здесть VCS Trigger - запуск сборки по коммиту, Schedule Trigger - запуск по расписанию.

VCS Trigger (запуск сборки по коммиту)

Schedule Trigger (запуск по расписанию)

Теперь мы видим список наших тригеров.

Список тригеров

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

Переходим на вкладку Build Steps (шаги сборки) и нажимаем кнопку Add build step (Добавить шаг сборки).

Build Steps (шаги сборки)

Все наши шаги будут работать путем выполнения скриптов из командной строки. Поэтому Runner type (тип запуска) выбираем Command Line (командная строка).

Поле Custom script (пользовательский сценарий) представляет собой bash скрипт. Он позволяет писать команды друг за другом в новой строке или через && в одной строке как простые Linux команды.

Описание всех командах можно прочитать в статье.

Первая команда будет CodeBrowser:

mkdir cb && phpcb -s . -o cb

CodeBrowser

Далее PhpDocumentor:

mkdir docs && phpdoc -d . -t docs

PhpDocumentor

Далее PHPLint:

find . -type f -name '*.php' -exec php -l '{}' ';'
log=`find . -type f -name '*.php' -exec php -l '{}' ';' | grep -v 'No syntax '`;

if [ x"${log}" != x ]; then
        exit 1;
else
        echo "No syntax errors detected in PHP scripts";
fi

PHPLint

Далее PHPDCD:

phpdcd .

PHPDCD

Далее PHPMD:

phpmd . text codesize,unusedcode,naming

PHPMD

Далее PHPCS:

find . -type f -name '*.php' -exec phpcs --standard=Symfony2 '{}' ';'
log=`find . -type f -name '*.php' -exec phpcs --standard=Symfony2 '{}' ';' | grep ERROR`;

if [ x"${log}" != x ]; then
        exit 1;
else
        echo "No syntax errors detected in PHP scripts";
fi

PHPCS

Далее PHPCPD:

phpcpd .

PHPCPD

Далее PHP Depend:

#!/bin/sh
log=`pdepend --summary-xml=summary.xml --jdepend-chart=jdepend.svg --overview-pyramid=pyramid.svg .`;
echo "${log}"
echo "<pre><code>${log}</code></pre>
    <p><img src='jdepend.svg' alt='jdepend.svg'/></p>
    <h5><strong>jdepend.svg</strong></h5>
    <p>Каждый из «шариков» отображает файлы определенного пакета-папки (в svg-файле при наведении на шарик всплывает подсказка с названием пакета). Размер шарика определяется количеством абстактных и конкретных классов в пакете.</p>
    <p>По оси Х откладываются значения <i>коэффициента абстракции</i> (abstraction ratio), по оси Y – <i>коэффициента нестабильности</i> (instability ratio).</p>
    <p>Прямая изображает оптимальное соотношение между этими коэффициентами.</p>
    <p><strong>Коэффициент абстракции</strong> (abstraction ratio, A) – отношение количества абстрактных классов и интерфейсов к общему числу классов в анализируемом пакете. Принимает значение от 0 до 1. Если равно 0, то пакет абсолютно конкретный, если 1 – то абсолютно абстрактный.</p>
    <p><strong>Коэффициент центростремительной связности</strong> (afferent couplings, Ca) – количество пакетов, которые зависят от классов текущего пакета.</p>
    <p><strong>Коэффициент центробежной связности</strong> (efferent couplings, Ce) – количество пакетов, от которых зависят классы текущего пакета.</p>
    <p><strong>Коэффициент нестабильности</strong> (instability ratio, I) – отношение коэффициента центробежной связности к сумме коэффициентов связности: I = Ce / (Ce + Ca). Показывает устойчивость пакета к изменениям. Принимает значение от 0 до 1. Если равен 0, то считается абсолютно стабильным пакетом (вообще не зависит от других пакетов), если 1 – то абсолютно нестабильным (полностью зависимый пакет, от которого не зависят другие).<p>
    <p><img src='pyramid.svg' alt='pyramid.svg' /></p>
    <h5><strong>pyramid.svg</strong></h5>
    <p>Данные отображены в виде пирамиды, потому что на ее внешних гранях располагаются коэффициенты, полученные как отношение значений характеристики с уровня ниже и текущего уровня. Значения коэффициентов «подкрашены» в зависимости от попадания его в диапазон значений – низкий, средний и высокий.</p>
    <p>Т.е., например, 0.176 = CYCLO / LOC.</p>
    <p>Теперь подробнее об используемых аббревиатурах.</p>
    <p>Слева:</p>
    <ul>
        <li><strong>CYCLO</strong> (Cyclomatic Complexity) – цикломатическая сложность пакетов (на основе числа ветвлений в коде типа if, for, foreach).</li>
        <li><strong>LOC</strong> (Lines Of Code) – число строк кода.</li>
        <li><strong>NOM</strong> (Number Of Methods+functions) – число методов классов + число функций.</li>
        <li><strong>NOC</strong> (Number Of Classes) – число классов.</li>
        <li><strong>NOP</strong> (Number Of Packages) – число пакетов.</li>
        <li><strong>AHH</strong> (Average Hierarchy Height) – средняя глубина иерархии.</li>
        <li><strong>AND</strong> (Average Number of Derived classes) – среднее число классов-наследников.</li>
    </ul>
    <p>Справа:</p>
    <ul>
        <li><strong>FANOUT</strong> (Number of Called Classes) – число использований классов (по-видимому, число созданий объектов классов).</li>
        <li><strong>CALLS</strong> (Number of Operation Calls) – число вызовов методов и функций.</li>
    </ul>" > pdepend.html

PHP Depend

Далее PHPLOC:

log=`phploc --log-xml phploc.xml .`;
echo "${log}"
echo "<pre><code>${log}</code></pre>" > phploc.html

PHPLOC

Далее нужно выполнить запуск модульных тестов (phpunit) и проверить покрытие кода тестами (code coverage). Но сначало надо добавить все зависимости. Это можно сделать с помощью Composer - менеджера php пакетов.

PHPUnit:

composer update
mkdir coverage
phpunit --coverage-text --coverage-html coverage

PHPUnit

У меня получился такой список шагов:

Все шаги сборки

Шаги можно включать / выключать, менять местами, двигая по списку.

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

Проект GTM bundle

И запустим сборку (Run):

Сборка проекта GTM bundle

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

Неудачная сборка проекта GTM bundle

Выбрав Build log (лог сборки), можно увидеть причину неудачи.

Неудачная сборка проекта GTM bundle

После исправления кода мы увидим прошедшую сборку.

Прошедшая сборка проекта GTM bundle

При клике на ссылку Success (успех), мы увидим статистику сборки.

Статистика сборки проекта GTM bundle

Здесь у нас должны быть вкладки с нашими отчетами, но их нет, т.к. мы не настроили Artifacts (артефакты) сборки.

Для настройки артефактов жмем Edit Configuration Settings (изменить настройки) и заполняем Artifact paths (пути артефактов).

cb => cb.zip
coverage => coverage.zip
docs => docs.zip
jdepend.svg
pyramid.svg
phploc.xml
summary.xml
pdepend.html
phploc.html

Конструкция cb => cb.zip как раз и формирует архивы, о которых мы говорили ранее. Эти архивы используются как своего рода локальные статические серверы для html файлов.

Artifact paths (пути артефактов)

TeamCity заботливо сразу же предложит сохранить изменения. Сохраняем и перезапускаем сборку. Ждем выполнения, переходим в Success (успех) и видим наши вкладки отчетов (Code Coverage, CodeBrowser, phpDocumentor, PHPLOC, PHP Depend).

Artifacts

Code Coverage:

Code Coverage

CodeBrowser:

CodeBrowser

phpDocumentor:

phpDocumentor

PHPLOC:

PHPLOC

PHP Depend:

PHP Depend

Поздравляю. На этом настройка практически закончена. Остался последний момент - настроить отправку писем с уведомлениями о проблемах, если такие будут.

Для настройки почты переходим в Administration (администрирование), затем Email Notifier (уведомления по почте) и заполняем поля. Т.к. я установил sendmail, мне достаточно указать локальный адрес сервера и email, с которого будет отправляться письма. Здесь небольшое уточнение, email должен включать в себя созданный домен (в моем случае ci.makedev.org).

Email Notifier (уведомления по почте)

Жмем Test connection (проверка подключения), в появившемся диалоговом окне вводим свой личный email, на которое отправится тестовое письмо, и видим сообщение об успешной отправке.

Email Notifier Connection successful!

Теперь нужно настроить отправку писем для созданного ранее пользователя. Жмем на имя пользователя в верхнем меню и попадаем на профайл пользователя.

Здесь мы можем заполнить имя пользователя и обязательно надо настроить его email, на который и будут отправляться письма.

Email address

Далее настриваем правила отправки. На этой же странице находим Watched Builds and Notifications, в нем поле Email Notifier и жмем Edit.

Здесь я отметил только поле Only notify on the first failed build after successful (отправлять только при первой неудачной сборки после успешной сборки).

Only notify on the first failed build after successful

Жмем сохранить (Save) и на этом все. Поздравляю, сервер CI создан и полностью настроен для одного проекта.

Но у меня есть еще похожие проекты, которые я хотел бы проверить. Можно повторять шаги для каждого проекта. А можно скопировать конфигурацию, что я и сделаю далее.

Для этого выбираем в главном меню Projects, затем жмем My bundles и Edit Project Settings. Так мы попадаем в настройки главного проекта.

Здесь мы видим Build Configurations и созданную сборку GtmBundle. Напротив нее жмем кнопку More и выбираем Copy build configuration... (копировать конфигурацию сборки).

Copy build configuration (копировать конфигцрацию сборки)

В появившемся диалоговом окне указываем название сборки и главный проект.

Copy build configuration (копировать конфигцрацию сборки)

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

General Settings

Осталось изменить github репозитойрий. Переходим на Version Control Settings. Видим подключенный репозиторий и жмем Edit.

Version Control Settings

Изменяем параметры репозитория.

Version Control Settings

TeamCity предложит использовать измененный репозиторий только для текущей сборки или использовать для всех сборок внутри проекта. Выбираю первый вариант.

Version Control Settings

Тестируем подключение

Version Control Settings

Теперь на главной странице проектов у меня 2 сборки внутри главного проекта.

Jquery bundle

Запускаем сборку и ждем завершения.

Jquery bundle

Осталось повторить это для остальных моих проектов.

GtmBundle, JqueryBundle, SimplePaginationBundle, TwitterBootstrapBundle

Хорошей практикой считается создать backup. TeamCity позволяет нам это сделать. Переходим в Administration, затем находим Backup. Жмем Start Backup. После этого получим ссылку для скачивания.

Backup

Теперь действительно все. Система создана, настроена и сохранена.

Другие инструменты Continuous Integration

Есть и другие инструменты платные и условно бесплатные, которые можно использовать для проверки кода (тесты, качество кода).

Отличный инструмент Travis CI для автоматического запуска юнит тестов (phpunit), при этом он позволяет проверять одновременно разные ветки и на разных версиях php одновременно.

Для использования достаточно разместить .travis.yml файл в корне github репозитория.

language: php

php:
  - 5.3
  - 5.4
  - 5.5
  - 5.6
  - 7.0
  - hhvm

matrix:
    allow_failures:
        - php: 7.0

sudo: false

install: travis_retry composer install --no-interaction --prefer-source

script: phpunit --coverage-text

Зайти в панель управления Travis CI и управлять проектом можно, используя github аккаунт.

Travis CI

Еще один интересный проект Scrutinizer чем-то похож на Travis CI, но является условно бесплатным. Позволяет бесплатно проверять качество кода, но тесты и покрытие кода доступны лишь в платной версии.

Scrutinizer CI

Каждый из этих серверов CI позволяет создать markdown (иконка со статусом проверки), который можно разместить в описании проекта.

markdown

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

Теги: Непрерывная интеграция, Continuous integration, Teamcity, Travis ci, Ci, Scrutinizer


Похожие статьи

Базовые HTML Meta теги

Установка Composer

Стив Макконнелл. Совершенный код — цитаты

Статьи о Symfony2