Pull to refresh

Continuous integration для php

Reading time 9 min
Views 14K
Эту статью написал мой добрый приятель и бывший коллега fred, работающий в команде программистов над большим и сложным проектом, который должен работать 24х7. Если кто-то решит пригласить его на хабр — с удовольствием вышлю его email по хабрапочте. Пожелания и комментарии приветствуются, а я обязуюсь передавать ответы автора в меру своих возможностей.

Меня давно посещала мысль запустить CI-сервер для рабочего проекта. База модульных тестов уже достаточно внушительных размеров, а количество людей в проекте немного увеличилось. Можно было бы наблюдать за тем, как изменяется покрытие тестами кода и соблюдаются стандарты кодирования. И наказывать провинившихся. Шучу.
Некоторое время назад была прочитана книжка Непрерывная интеграция. Улучшение качества программного обеспечения и снижение риска и статья Quality Assurance Tools for PHP, которые и послужили отправной точкой.

Сервер непрерывной интеграции с некоторой периодичностью опрашивает репозиторий проекта на наличие изменений. Если изменения найдены, то он
  1. генерирует документацию в формате phpdoc
  2. проверяет код на соответствие стандартам кодирования
  3. запускает тесты
  4. по результатам тестов и анализа кода строит метрики, а также динамику изменений этих метрик
  5. уведомляет всех заинтересованных в случае успешной или неуспешной сборки

В качество интеграционного сервера был выбран CruiseControl вместе с плагином для php — http://phpundercontrol.org/.

Установка CruiseControl на debian-like машине


Если вы ставите CruiseControl в другой операционной системе, то действия примерно такие-же: установить java, распаковать CruiseControl, сделать сценарии запуска и остановки сервера.
Для запуска самого CruiseControl нам надо установить Java-машину:

sudo apt-get install sun-java6-bin

и прописать её в переменных окружения:

export JAVA_HOME=/usr/lib/jvm/java-6-sun

Сначала создаем в системе пользователя, из-под которого будет запускаться CruiseControl:

sudo adduser cruisecontrol

К сожалению свежих .deb пакетов с CruiseControl я не нашел, а у самого пока руки не дошли сделать, поэтому качаем последнюю версию и распаковываем в директорию по желанию:

s.galkin@java-galkin:/opt$ sudo wget http://sourceforge.net/projects/cruisecontrol/files/CruiseControl/2.8.2/cruisecontrol-bin-2.8.2.zip/download
s.galkin@java-galkin:/opt$ sudo unzip cruisecontrol-bin-2.8.2.zip
s.galkin@java-galkin:/opt$ sudo mv cruisecontrol-bin-2.8.2 cruisecontrol

и делаем владельцем директории нашего пользователя:

s.galkin@java-galkin:/opt$ sudo chown cruisecontrol /opt/cruisecontrol -R

Берем шелл-скрипт из руководства по установке CruiseControl в unix. и сохраняем его в файл /etc/init.d/cruisecontrol .
Теперь можно запустить CC и проверить, что он работает:

s.galkin@java-galkin:/opt$ sudo /etc/init.d/cruisecontrol start

Веб-интерфейс должен быть доступен по адресу http://yourhostname:8080/
Остановить сервер можно соответственно:

s.galkin@java-galkin:/opt$ sudo /etc/init.d/cruisecontrol stop

Переходим к установке php-части.
У вас наверняка уже стоит xdebug и pear, но если нет:

sudo apt-get install php5-xdebug
sudo apt-cache search php pear

Если у вас достаточно много кода и тестов, стоит увеличить количество выделяемой php-процессу памяти:

max_execution_time = 0
memory_limit = 512M

Установка php-пакетов


Теперь устанавливаем пакеты для документирования, тестирования, и оценки кода и сам phpUnderControl:

pear install PhpDocumentor
pear install PHP_CodeSniffer
pear channel-discover pear.phpunit.de
pear install phpunit/PHPUnit
pear channel-discover components.ez.no
pear install --alldeps phpunit/phpUnderControl-beta

Если вы ставите все на отдельной машине, то не забудьте поставить все библиотеки, от которых зависит ваш проект. Заодно можно проверить, что phpunit, phpcs, phpdoc выполняются с теми наборами параметров, которые указаны в build.xml.
Делаем phpUnderControl плагином CruiseControl’а, указав в качестве аргумента директорию, где установлен последний:

phpuc install /opt/cruisecontrol

Настройка проекта


Открываем конфигурационный файл config.xml, находящийся в корневой директории CruiseControl и заменяем его следующим:
<cruisecontrol>
  <property name="ant.dir" value="apache-ant-1.7.0"/>
  <property name="work.dir" value="/opt/cruisecontrol"/>
 
  <property name="logs.dir" value="${work.dir}/logs/${project.name}" />
  <property name="artifacts.dir" value="${work.dir}/artifacts/${project.name}" />
 
  <property name="project.dir" value="${work.dir}/projects/${project.name}" />
  <property name="project.code.dir" value="${project.dir}/source"/>
  <property name="project.build.dir" value="${project.dir}/build"/>
  <property name="project.logs.dir" value="${project.build.dir}/logs"/>
  <property name="project.coverage.dir" value="${project.build.dir}/coverage"/>
  <property name="project.api.dir" value="${project.build.dir}/api"/>
  <property name="project.build.file" value="${project.code.dir}/misc/ci/build.xml"/>
 
  <property name="status.file" value="${logs.dir}/${project.name}/status.txt"/>
 
  <project name="yourProjectName">
    <listeners>
      <currentbuildstatuslistener file="${status.file}"/>
    </listeners>
 
    <bootstrappers>
      <svnbootstrapper localWorkingCopy="${project.code.dir}"/>
    </bootstrappers>
 
    <modificationset>
      <svn localWorkingCopy="${project.code.dir}"/>
    </modificationset>
 
    <schedule interval="300">
      <ant anthome="${ant.dir}" buildfile="${project.build.file}">
        <property name="project.code.dir" value="${project.code.dir}/"/>
        <property name="project.build.dir" value="${project.build.dir}"/>
        <property name="project.logs.dir" value="${project.logs.dir}"/>
        <property name="project.api.dir" value="${project.api.dir}"/>
        <property name="project.coverage.dir" value="${project.coverage.dir}"/>
      </ant>
    </schedule>
 
    <log dir="${logs.dir}">
      <merge dir="${project.logs.dir}"/>
    </log>
 
    <publishers>
      <currentbuildstatuspublisher file="${logs.dir}/buildstatus.txt"/>
      <artifactspublisher dir="${project.coverage.dir}" dest="${artifacts.dir}" subdirectory="coverage" />
      <artifactspublisher dir="${project.api.dir}" dest="${artifacts.dir}" subdirectory="api" />
      <execute command="phpuc graph ${logs.dir} ${artifacts.dir}" />
    </publishers>
  </project>
</cruisecontrol>

Я перенес все пути к файлам и директориям в переменные на самый верх конфигурационного файла, чтобы при надобности было легко их поправить в одном месте.
В качестве сборщика я использовал ant (он явно указан в config.xml в ноде /cruisecontrol/project/schedule), который мне немного знаком. Если ваш проект уже использует phing (что более логично для php-проектов, чем ant), то CruiseControl его отлично поддерживает.
В ноде /cruisecontrol/project/publishers можно кроме обработки результатов тестов и метрик можно также посылать в случае неудачных сборок (возможно и удачных тоже) уведомления заинтересованным лицам на почту/в жаббер.
Подробное описание конфигурационного файла можно найти по ссылке CruiseControl Configuration Reference
Переходим к самому сценарию сборки проекта.
<project name="yourProjectName" default="build">
  <target name="prepare">
    <delete dir="${project.build.dir}"/>
    <mkdir dir="${project.logs.dir}"/>
  </target>
 
  <target name="phpdoc">
    <exec executable="phpdoc" dir="${project.build.dir}" failonerror="false">
      <arg line="
        -o HTML:frames:DOM/earthli
        -ti '${ant.project.name} documentation'
        -q
        -t ${project.api.dir}
        -d ${project.code.dir}
      "/>

    </exec>
  </target>
 
  <target name="phpcs">
    <exec executable="phpcs" dir="${project.build.dir}" failonerror="false" output="${project.logs.dir}/checkstyle.xml">
     <arg line="
        --report=checkstyle
        --standard=PEAR
        ${project.code.dir}
      "/>

    </exec>
  </target>
 
  <target name="phpunit">
    <exec executable="phpunit" dir="${project.build.dir}" failonerror="true">
      <arg line="
        --log-xml ${project.logs.dir}/phpunit.xml
        --log-pmd ${project.logs.dir}/phpunit.pmd.xml
        --log-metrics ${project.logs.dir}/phpunit.metrics.xml
        --coverage-xml ${project.logs.dir}/phpunit.coverage.xml
        --coverage-html ${project.coverage.dir}
        phpucAllTests ${project.code.dir}/utests/AllTests.php
      "/>

  </exec>
  </target>
 
  <target name="build" depends="prepare,phpdoc,phpcs,phpunit"/>
</project>

Все переменные с путями были переданы из config.xml
<ant anthome="${ant.dir}" buildfile="${project.build.file}">
    <property name="project.code.dir" value="${project.code.dir}/"/>
    <property name="project.build.dir" value="${project.build.dir}"/>
    <property name="project.logs.dir" value="${project.logs.dir}"/>
    <property name="project.api.dir" value="${project.api.dir}"/>
    <property name="project.coverage.dir" value="${project.coverage.dir}"/>
</ant>

Я решил сразу положить build.xml в репозиторий, чтобы в будущем его было легче обновлять.
Не забудьте правильно указать путь к нему из config.xml (атрибут buildfile в ноде /cruisecontrol/project/schedule/ant)
Теперь выгружаем в папку /opt/cruisecontrol/projects/yourProjectName/source из репозитория ваш проект и запускаем CruiseControl. Все!

Существующие проблемы


При наличии warning/notice, Codesniffer не подавляет их и в результате получается невалидный xml файл, который CruiseControl не может прочитать. Вообще при любом непонятном поведении можно посмотреть лог /opt/cruisecontrol/cruisecontrol.sh

Итог


В результате работы сервера непрерывной интеграции мы получаем следующие преимущества:
  • все разработчики своевременно уведомляются о неудачном построении
  • динамика изменений количества тестов
  • динамика изменений процента кода, покрытого тестами
  • анализ покрытия тестами конкретных классов
  • нарушение стандартов кодирования
  • актуальную документацию по внутреннему api
  • статический анализ кода: слишком длинные методы, слишком сложная условная логика, …

Полезные ссылки


  1. The CruiseControl Best Practices Series
  2. Quality Assurance Tools for PHP
  3. RunningCruiseControlFromUnixInit
  4. Setting up phpUnderControl
  5. phpUnderControl
  6. CruiseControl Configuration Reference
  7. Запуск CodeSniffer с стандартами кодирование, лежащими в нестандартной директории
Tags:
Hubs:
+72
Comments 44
Comments Comments 44

Articles