Журнал LinuxFormat - перейти на главную

LXF129:Cijoe

Материал из Linuxformat
(Различия между версиями)
Перейти к: навигация, поиск
(викификация, оформление)
 

Текущая версия на 09:32, 5 апреля 2011

Непрерывная интеграция Реальный опыт внедрения технологии контроля качества кода

Содержание

[править] Cijoe: Следы Java в проекте Django

Помимо легендарной громоздкости, проекты Java отличаются выверенным подходом к разработке. Python есть чему у них поучиться, считает Роман Богородский.

Continuous integration (непрерывная интеграция, CI) – практика разработки программного обеспечения, суть которой заключается в частом объединении кода различных разработчиков и проверке целостности проекта. Обычно это реализуется следующим образом: исходные коды проекта копируются из репозитория и собираются, а затем выполняются тесты.

Традиционно, сборка запускается каждый раз при внесении изменений в репозиторий. Одним из главных преимуществ такого подхода является уверенность в том, что проект собирается и тесты успешно выполняются, и в случае появления в репозитории вредоносного изменения это будет заметно сразу. Техника CI очень популярна среди Java-проектов, и в мире Java существует большое количество программного обеспечения для ее реализации, начиная от тяжелой артиллерии вроде AnthillPro и заканчивая удобными и простыми в конфигурации инструментами, например, Hudson.

Применение CI для Python почему-то развито значительно меньше, чем в мире Java, и в этой статье хотелось бы поделиться опытом внедрения CI-сервера в Python-проект.

[править] Поставим задачу

Наш проект представляет из себя типичное web-приложение, написанное с использованием каркаса Django (LXF105‑108), задействующее базу данных и несколько внешних сервисов. Тесты организованы в стандартном для Django стиле: в каждом приложении [application] находится файл tests.py с тестами, которые запускаются командой manage.py test.

Исходные тексты проекта хранятся в репозитории Git. Это во многом повлияло и на выбор CI-сервера – им стал cijoe (http://github.com/defunkt/cijoe), предлагающий простую интеграцию с Git-репозиториями.

В простейшем случае, для использования cijoe достаточно указать, какой командой запускать тесты, к примеру:

git config --add cijoe.runner “./manage.py test app1 app2 appN»

Почему после ./manage.py test идет список приложений? Дело в том, что если запустить ./manage.py test без аргументов, он выполнит тесты для всех приложений, которые указаны в переменной INSTALLED_APPS в settings.py, в том числе и для стандартных, включенных в состав Django, например, Django.auth. Тестирование приложений из дистрибутива Django не является целесообразным, поэтому мы явно перечисляем все приложения, которые требуется тестировать.

Теперь можно запустить cijoe следующим образом:

cijoe склонированный_репозиторий

На порту 4567 машины, где выполняется cijoe, станет доступна консоль cijoe, интуитивно понятная в использовании. На данном этапе будет возможно запускать тесты и проверять результаты вручную. Сборка считается успешной, если команда, прописанная в cijoe.runner, завершится с кодом возврата 0.

[править] Автоматизация

Гораздо удобнее, когда сборки запускаются автоматически после каждого проталкивания [push] изменений в центральный репозиторий. Для этого последний нужно сконфигурировать таким образом, чтобы после выполнения данной операции совершался POST-запрос по URL, на котором доступен cijoe, к примеру, http://ci.example.com/.

Еще одним важным моментом является уведомление разработчиков о том, что тесты провалились – ведь какой толкот CI-сервера, если никто не узнает результаты? Все это легко осуществимо посредством «ловушек» [hook]: в случае проваленных тестов cijoe запустит .git/hooks/build-failed, в случае успеха – .git/hooks/build-worked.

Кто-то может спросить: почему нельзя использовать локальные «ловушки» и запускать тесты перед каждой фиксацией изменений [commit] на машине разработчика? Такой вариант возможен, но в рамках рассматриваемой проблемы у него есть несколько недостатков:

  • Выполнение тестов может занимать довольно много времени, и у разработчиков возникнет соблазн выключать их с помощью опции --no-verify для git commit.
  • Придется полагаться на то, что разработчики не будут злоупотреблять --no-verify.
  • Хранить «ловушки» централизованно довольно нетривиально.
  • Далеко не все разработчики влияют на выполнение тестов: например, дизайнеры, занимающиеся в проекте графикой, вряд ли способны своими изменениями повлиять на результаты выполнения тестов.

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

PROJECT_NAME=”foobar-ng”
PROJECT_DIR=”/home/joe/foobar-ng”
COMMITTERS=”committers@foobarng.example.com”
TEST_RUNNER=”test.sh”
echo “To: ${COMMITTERS}
Subject: ${PROJECT_NAME}: Tests Failed!
Last commit:
`git log -1 --pretty=short`
Tests output:
`cd ${PROJECT_DIR} && ./test.sh 2>&1`
“ |msmtp `echo ${COMMITTERS}|sed 's|,| |'`

В случае возникновения ошибок скрипт посылает письмо с использованием утилиты msmtp. В тело сообщения включается вывод команды, запускающей тесты, и информация о последнем изменении в коде.

Даже в такой простой конфигурации CI-сервер представляется чрезвычайно полезным, так как дает своевременную информацию о состоянии проекта и заставляет разработчиков проверять результаты выполнения тестов перед проталкиванием изменений в центральный репозиторий, или, если это не было сделано, своевременно исправлять ошибки.

[править] Оценим охват

Не менее интересным представляется вычисление такой метрики, как покрытие кода. Задача сбора этой информации тоже решается довольно просто. Процент покрытия кода в Python можно узнать с помощью модуля coverage.py (easy_install coverage).

Для этого потребуется написать небольшую обертку для средства запуска тестов Django.

 import os
 import shutil
 import sys
 import unittest
 import coverage
 from django.test.simple import run_tests as django_test_runner
 from django.conf import settings
 def test_runner_coverage(test_labels, verbosity=1, interactive=True, extra_tests=[]):
    coverage.use_cache(0)
    coverage.start()
    test_results = django_test_runner(test_labels, verbosity,
 interactive, extra_tests)
    coverage.stop()
    coverage_modules = []
    for module in test_labels:
       for i in (“views”, “urls”, “models”):
        try:
           coverage_modules.append(__import__(%s.%s” % (module, i), globals(), locals(), ['']))
        except ImportError:
           # not all apps have urls.py module, so it's ok to ignore some import errors
           pass
   coverage.report(coverage_modules, show_missing=1)
   return test_results

Данный «тестопускатель» собирает информацию о покрытии кода для тестируемых приложений и печатает эти сведения в стандартный вывод после того, как все тесты были запущены.

Чтобы ./manage.py test использовал приведенный выше код, нужно добавить в settings.py следующую опцию:

TEST_RUNNER='tests.test_runner_with_coverage'

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

[править] И так далее

Думаю, мне удалось показать, что довольно скромными силами можно сделать вполне функциональный CI-сервер с автоматическими сборками по событию, уведомлениями о сломанных сборках по почте и отчетами о покрытии кода тестами. Существенным плюсом также является расширяемость cijoe – например, вместо уведомлений по почте можно использовать Jabber. Возможны и более интересные применения: например, может оказаться полезным при каждой сборке сохранять информацию о покрытии кода тестами и анализировать динамику соотношения объема кода и его покрытия. К примеру, если объем кода увеличивается, а покрытие уменьшается, это может быть признаком, что разработчики перестали писать тесты для нового функционала.

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

Персональные инструменты
купить
подписаться
Яндекс.Метрика