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

LXF129:Cijoe

Материал из Linuxformat
Перейти к: навигация, поиск
Непрерывная интеграция Реальный опыт внедрения технологии контроля качества кода

Содержание

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. Возможны и более интересные применения: например, может оказаться полезным при каждой сборке сохранять информацию о покрытии кода тестами и анализировать динамику соотношения объема кода и его покрытия. К примеру, если объем кода увеличивается, а покрытие уменьшается, это может быть признаком, что разработчики перестали писать тесты для нового функционала.

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

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