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

