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

HowTo: continuous integration проекта на Django с помощью TeamCity

Время на прочтение8 мин
Количество просмотров14K

Введение


В процессе разработки, создавая новый функционал, всё чаще широкими мазками стал задевать старый код чем разрушал логику его работы. Это заставило всё-таки написать юнит и интеграционные тесты для старого кода и автоматизировать их запуск, т.к. гонять руками все тесты как-то грустно. Как раз вспомнилось недавнее руководство по CI Django в Jenkins и довольно старое по Webtest в Django. В итоге была совершена попытка поднять Дженкинса, но он как-то на моей убунте не взлетел и я грешным делом вспомнил про TeamCity. «Раз уж пишу в PyCharm и нашёл к нему подход, то, наверно, и TeamCity осилю, ведь конторка-то одна!» — подумалось мне… В общем-то я оказался прав, и, пока мне позволяет карма, решил подарить вам ультраполезный (и мегаподробный), в отличие от моего предыдущего, мануал :)

Итого: кому требуется руководство по поднятию интеграционного сервера TeamCity, и тестирование в нём Django проектов c тестами nose и webtest в виртуальном окружении python с автоматическим его (окружения) обновлением — добро пожаловать под кат.

Осторожно! Для работы TeamCity требуется (согласно документации) sun/oracle версия JVM…

Теория


Виртуальное окружение python с помощью virtualenv

Система на моём ПК для разработки удовлетворяет всем требованиям моего проекта, но это далеко не так на сервере для тестирования (даже python там 2.6, а мне в итоге понадобился 2.7), потому пришлось озаботиться виртуализацией окружения, чтобы спокойно ставить то, что нужно мне, кроме того это избавляет от необходимости обладать властью root'а или sudoer'а.

Для создания виртуального окружения нужно установить virtualenv или скачать и извлечь из архива virtualenv.py. Мы будем использовать второй вариант, т.к. это совсем избаляет нас от необходимости пользоваться власть суперпользователя. Да, в системе всё-таки должен быть установлен python.

python virtualenv.py -p python2.7 .env


Здесь -p python2.7 задаёт необходимую версию python (тонкость в том, что нужные версии питона должны быть установлены в самой системе, но не быть при этом «питоном по-умолчанию», скрипт не сможет их сам вытянуть и установить), а .env каталог в котором будет размещено окружение. Далее, чтобы использовать python и модули из окружения нужно вызывать их через .env/bin/python если же писать это лень — можно выполнить

source .env/bin/activate


который заменяет переменную окружения PATH.

Автоматизация через fabric

Кроме самого питона установленного на целевой системе, нам также потребуется модуль fabric. То есть на сервере тестирования придётся выполнить команду sudo pip install fabric или sudo easy_install fabric, при установке потянет pycrypto которому для сборки нужны сорцы питона из python-dev, так что сначала нужно поставить его. Fabric позволяет исполнять скрипты на python, которые выполняют действия на локальной, либо удалённой (через SSH) системе. После установки в системе появится приложение fab, которое ищет файл fabfile.py в папке вызова и исполняет указанный скрипт.

Например:

fab test


выполнит выполнит функцию test из файла fabfile.py.

Nose и webtest в django

Теперь немного о самих тестах. У django есть свои юнит-тесты и функционалные тесты (с помощью тестового клиента), но есть в них и недостатки о которых писано не однократно. В общем я стал использовать nose для юнит-тестирования и webtest для функционального. Во введении я давал ссылку на статью про webtest, там неплохо показано как это работает. Нам для тестирования понадобятся следующие модули (потом этот список мы добавим в файл со списком модулей для автоматической установки через pip -r):

coverage>=3.0
nose
webtest
django-nose
django-webtest


Кроме того django_nose нужно добавить в INSTALLED_APPS файла settings.py вашего django проекта, либо в том же файле указать TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'.

Внимание! Если используется south, то django_nose надо добавлять после него.

Собственно, если есть юнит-тесты, уже написанные в джанговской системе тестирования — их можно не трогать, всё будет работать и так. Как писать функциональные тесты через webtest можно прочитать в вышеупомянутой статье.

Запускается тестирование как обычно:

python manage.py test


Эта команда выполнит как nose тесты так и webtest тесты, если названия файлов, классов и функций с тестами имеют в названии слово "test" — это бонус от nose. Кроме того nose может использовать coverage для рассчёта покрытия кода тестами (функциональные webtest тесты он не понимает, т.к. тесты работают с кодом не напрямую, а через генерённые страницы). Django-nose позволяет нам использовать опции запуска nose тестов. Для этого их можно прописать в файле settings.py проекта. Например:

NOSE_ARGS = [
    '--nocapture',
    '--with-coverage',
    '--cover-html',
    '--cover-html-dir=%s' % os.path.join(PROJECT_PATH, 'cover', 'unit'),
    '--cover-package=django_dir'
]


Это заклинание позволит сгенерировать отчёт о покрытии тестами в HTML формате и поместить его в подкаталог cover/unit вашего проекта.

Практика


Структура проекта

Мой проект имеет следующую структуру папок:

.
..
project_dir
project_dir/django_dir
project_dir/build
project_dir/build/pipreq.txt
project_dir/fabfile.py
project_dir/virtualenv.py


Где project_dir содержит в себе всякие полезные штуки типа каталог с виртуальным окружением, файл фабрики и прочие полезные штуки, а уже в подкаталоге django_dir располагается собственно сам проект на django. В систему контроля версий (в моём случае это GIT) попадает всё начиная с project_dir с небольшими исключениями вроде каталога с виртуальным окружением.

Выполнение тестов

Собственно наша цель научить TeamCity после обнаружения изменений в репозитории (что он умеет сам) протестировать наш обновлённый django проект (чего он не умеет) и сообщить нам о результатах (тут ему нужно тоже немного помочь).

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

Код project_dir/fabfile.py:

# -*- coding: utf-8 -*-

from fabric.api import local

def test():
    """
    Запуск тестов.
    """
    local('python virtualenv.py -p python2.7 .env') # создаём виртуальное окружение
    local('./.env/bin/pip install -q -E .env -r build/pipreq.txt') # ставим модули окружения
                                                      # перечисленные в build/pipreq.txt
    local('./.env/bin/python django_dir/manage.py test --noinput') # запускаем тесты


Обратите внимание, что virtualenv.py лежит в корне проекта, чтобы его не требовалось устанавливать в системе.

Необходимое содержание project_dir/build/pipreq.txt:

django
coverage>=3.0
nose
teamcity-nose
webtest
django-nose
django-webtest
...много много ваших модулей которые постоянно добавляются/меняются...


Обратите внимание в файле перечислен ранее не упоминавшийся teamcity-nose, этот модуль и помогает передать TeamCity результаты нашего тестирования. Он не требует никакой настройки и подключения — запускается автоматически когда находит переменную окружения TEAMCITY_PROJECT_NAME которую создаёт TeamCity во время работы.

Собственно всё готово к автоматическому тестированию. Чтобы его начать достаточно выполнить команду:

fab test


в общем корне проекта.

Установка и настройка TeamCity

Я сильно заморачиваться не стал, чего и вам рекомендую — просто скачал tar.gz вариант со страницы загрузки. Весит всё это счастье неприличные 333МБ. Но в комплекте несёт кроме самого сервера интеграции ещё и Tomcat, что избавит нас от геморроя с настройками серверов и прочей лабудой.

Есть у TeamCity свойство (упомянутое в начале), которое может кому-нибудь не понравиться — он требует для работы сановскую/оракловую версию JVM.

Как получить её на Ubuntu можно почитать к примеру тут.

Собственно после скачивания распаковываем это добро там, где оно будет работать (сами данные по-умолчанию будут храниться в подкаталоге .BuildServer домашнего каталога пользователя под которым запущен сервер). Запускается сервер командой bin/runAll.dh start, убивается соответственно bin/runAll.dh stop (если находимся в папке с распакованным архивом). Сервер по-умолчанию крутится на 8111 порту, но есть ещё build agent который стартует на 9090 порту. Первый можно изменить в файле conf/server.xml, второй в файле buildAgent/conf/buildAgent.dist.properties (образец) и buildAgent/conf/buildAgent.properties (конкретный агент).

Все остальные настройки (ну про которые я расскажу) будут делаться через веб интерфейс. В общем стартуем teamcity и идём в браузер (http://localhost:8111 если запускался локально). В зависимости от мощности системы вы либо увидите лицензионное соглашение, либо сообщение:



и нужно будет подождать пока оно сменится лицензионным соглашением.

Далее будет предложено создать администраторский аккаунт. После чего мы увидим уже саму систему TeamCity.

Тут наc интересует создание проекта. Там всё просто — название и описание. После мы попадаем на страницу настроек проекта.



На вкладке VCS Roots (VCS — version control system) нужно задать новый репозиторий. Точнее указать откуда система будет брать код для тестирования. Там длинная портянка которую нужно заполнить вашими данными. Все поля подписаны, имеются линки на страницы помощи, так что ничего сверхчеловеческого там нет. Тут же можно проверить работоспособность соединения.

Далее на вкладке General нужно создать новую Buid Configuration — краеугольный камень нашего тестирования.



Build counter — номер с которого начнётся нумерация билдов, а точнее номер билда который будет присвоен следующей сборке. Можете смело ставить 1, тут просто скриншот не свежий :)

Из важых полей на первой странице здесь Artifact paths который указывает системе как найти файлы возникающие в результате сборки/тестирования. В нашем случае это отчёты по покрытию тестами. В моём случае здесь достаточно ввести django_dir/cover/**/*. На второй странице выбираем наш ранее заданный репозиторий.

Нажав на Add Build Step нужно будет создать первый (и, в нашем случае единственный) шаг сборки (в нашем случае тестирования).



Собственно требуемый набор настроек вы видите на скрине. Сохраняем.

Далее справа в вертикальном меню нужно выбрать Build Triggering и добавить запуск нашей сборки при изменении репозитория:



Ну и напоследок возвращаемся к настройкам самого проекта, идём на вкладку Report Tabs и создаём новую вкладку:



это позволит TemCity отображать наши отчёты в своём интерфейсе.



Вкладка появится тогда, когда в указанном месте будет указанный файл, так что если её нет — проверьте настройки и посмотрите действительно не ли отчёты лежат там где должны.

Вот и всё, остаётся в самом верхнем меню в Projects перейти к нашему проекту и нажать Run у билда, для первого прогона.

Красивости

Все менеджеры любят графики, потому чтоб было, что показать нужно сделать несколько картинок. Увы картинки эти добавляются истинно программерско-админским методом — внесением изменений в XML конфиг. С руководством можно ознакомиться здесь. Чуствуется что в этом месте разработчики изрядно отдохнули (могли бы и формы сделать чесслово). Т.к. очевидного способа узнать один из важнейших параметров buildTypeId я не нашёл. Самое простое — посмотреть в адресной строке браузера: localhost:8111/viewType.html?tab=buildTypeStatusDiv&buildTypeId=bt2.

Приведу конфиг двух своих графиков:

<?xml version="1.0" encoding="UTF-8"?>
<settings>
  <report-tabs>
    <report-tab title="Coverage" basePath="/unit/" buildTypeId="bt2">
      <revisionRule name="lastFinished" revision="latest.lastFinished" />
    </report-tab>
  </report-tabs>
  <custom-graphs>
    <graph title="Build Duration" defaultFilters="showFailed" seriesTitle="key"> <!-- Беспонтовый график для тестов -->
      <valueType key="BuildDuration" title="main test" buildTypeId="bt2"/>
    </graph>
    <graph title="Test Passes" defaultFilters="showFailed" seriesTitle="key"> <!-- Результаты тестов -->
      <valueType key="FailedTestCount" title="fail" buildTypeId="bt2"/>
      <valueType key="PassedTestCount" title="pass" buildTypeId="bt2"/>
    </graph>
  </custom-graphs>
</settings>


Это файл ~/.BuildServer/config/django_dir/plugin-settings.xml. Изменения в файле подхватываются на лету.

Выглядит это как-то так:



А вот так выглядит отчёт по покрытию:



Список сборок:



Подробности по сборке:



Заключение


Прошу прощения за «многобукаф,» но так уж вышло. Хотелось описать доступно. В итоге мы имеем автоматизированное тестирование (модульное и интеграционное), кроме того проверяется ещё и наличие необходимых модулей и вообще «поднимаемость» проекта. Разборки с реагированием TemaCity на различные результаты сборки я оставляю на вашей совести. В этой части есть чему порадоваться — и стандартное мыльное уведомление, и уведомление через jabber, и создание релизного пуша.

Удачной разработки!
Теги:
Хабы:
Всего голосов 30: ↑27 и ↓3+24
Комментарии16

Публикации

Истории

Работа

Python разработчик
121 вакансия

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

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань