Как стать автором
Обновить

Непрерывная интеграция: Hudson + PHPUnit

Время на прочтение 4 мин
Количество просмотров 2.6K
Существует цепочка в мозгу: мы напишем юнит тесты, затем эти тесты нам расскажут если мы что-то сломали, затем они нам почту будут отправлять о том, что проект поломался.
Это ничто иное, как иллюстрация непрерывной интеграции (Continious Integration) нычне крайне модного направления гибкой разработки. Единственный недостающий элемент цепочки — «КАК». Ниже коротенький рецепт, как бы отвечающий «очень просто».

В рамках рецепта я не буду рассматривать как поставить hudson, как написать тесты на phpunit. Все это доступно и удобно описано в соответствующих мануалах, и других хабрапостах.
Скажу только, что Hudson выбран потом, что он достаточно функционален, популярен и бесплатен.

Ингредиенты


1. Написанные тесты на PHPUnit и собранные в готовый Suite файл, назовем его All.php
2. Установленный PHPUnit, который можно вызвать из командной строки
3. Установленный Hudson, и добавлен job для нашего проекта

Рецепт


1. Указываем путь к репозиторию в разделе Source Code Management. Hudson поддерживает svn, git и cvs
2. Указываем расписание сборок\проверок cron строкой в разделе Build Triggers.
3. В разделе Build указываем вызов наших тестов
phpunit --log-junit $WORKSPACE/phpunit.xml AllSuite $WORKSPACE/tests/All.php

4. В разделе Post-build Actions ставим checkbox для Public JUnit test result report, а в Test report XMLs указываем phpunit.xml
5. В том же разделе Post-build Actions ставим checkbox для E-mail Notification. Указываем получателей через пробел в строке recipients. И ставим checkbox на Send e-mail for every unstable build

Если бы всё было совсем просто, то на этом шаге можно было бы и закончить, но, к сожалению, формат, в котором phpunit предоставляет результаты, и тот, которого ожидает hudson, немного отличаются. Hudson не понимает того, что может быть несколько вложенных Suite. И выдает вот такое ошибку: None of the test reports contained any result. Поэтому требуется бубен.

Идея проста xml, который генерит phpUnit, конвертировать в xml, который нужен для hudson.
Для этого я написал вот такой скрипт, который и выполнит для нас эту работу
  1. <?php
  2. if ($_SERVER['argv'][1] || $_GET['unit']) {
  3.  
  4.     $fileUnit = $_SERVER['argv'][1] ? $_SERVER['argv'][1] : $_GET['unit'];
  5.     $fileHudson = $_SERVER['argv'][2] ? $_SERVER['argv'][2] : $_GET['hudson'];
  6.  
  7.     if (file_exists($fileUnit)) {
  8.         new PHPUnit2Hudson($fileUnit, $fileHudson);
  9.     } else {
  10.         die('phpunit xml file not exists '. $file);
  11.     }
  12.  
  13. } else {
  14.     die("determine file. use command: php -f phpunit2hudson.php -- phpunit.xml hudson.xml");
  15. }
  16.  
  17. class PHPUnit2Hudson {
  18.  
  19.     private $xml;
  20.     private $cases = array();
  21.  
  22.     private $countAssertions = 0;
  23.     private $countFailures = 0;
  24.     private $countErrors = 0;
  25.     private $countTime = 0;
  26.  
  27.     function  __construct($fileUnit, $fileHudson) {
  28.          
  29.          $oldLevel = error_reporting(0);
  30.          $this->xml = simplexml_load_file($fileUnit);
  31.          error_reporting($oldLevel);
  32.          
  33.          if (!$this->xml->testsuite)
  34.             die('invalid phpunit xml file');
  35.          
  36.          foreach($this->xml->testsuite->attributes() as $key => $value) {
  37.                 if ($key == 'failures') $this->countFailures = intval($value);
  38.                 if ($key == 'errors') $this->countErrors = intval($value);
  39.                 if ($key == 'time') $this->countTime = floatval($value);
  40.                 if ($key == 'assertions') $this->countAssertions = intval($value);
  41.          }
  42.  
  43.          $this->getCases($this->xml);
  44.  
  45.          file_put_contents($fileHudson, $this->composeHudson());
  46.     }
  47.  
  48.     function getCases(SimpleXMLElement $node) {
  49.         if (isset($node->testcase))
  50.         foreach ($node->testcase as $case) {
  51.            $this->cases[] = $case;
  52.         } elseif (isset($node->testsuite))
  53.         foreach ($node->testsuite as $suite) {
  54.             $this->getCases($suite);
  55.         }
  56.     }
  57.  
  58.     function composeHudson() {
  59.           $xmlHudson = "<testsuites>\n";
  60.           $xmlHudson .= '<testsuite name="Hudson_Suite" file="All.php" tests="'.sizeof($this->cases);
  61.           $xmlHudson .='" assertions="'.$this->countAssertions.'" ';
  62.           $xmlHudson .='failures="'.$this->countFailures.'" ';
  63.           $xmlHudson .='errors="'.$this->countErrors.'" ';
  64.           $xmlHudson .='time="'.$this->countTime.'">'."\n";
  65.           foreach ($this->cases as $case) {
  66.                $xmlHudson .= $case->asXML()."\n";
  67.           }
  68.           $xmlHudson .= "</testsuite></testsuites>";
  69.  
  70.           return $xmlHudson;
  71.     }
  72. }

6. Добавляем себе этот скрипт под svn и в раздел build вписываем еще одно действие
php -f $WORKSPACE/tests/phpunit2hudson.php -- $WORKSPACE/phpunit.xml $WORKSPACE/test.xml

7. Меняем в Junit Test report XMLs c phpunit.xml на test.xml

Вот и всё, смотрим на красивые графики выполненных тестов, и получаем емейлы, если какой-то из тестов лёг.

Надеюсь, что данная статья поможет тем, кто хотел внедрить continious integration в свою php разработку, но думал, что это слишком сложно
Теги:
Хабы:
+29
Комментарии 19
Комментарии Комментарии 19

Публикации

Истории

Работа

PHP программист
175 вакансий

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн