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

    Введение


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

    Удачной разработки!
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 16

      +2
      Кто еще прочитал How to NO continuous integration?
        0
        стоит с большой «П» написать? :)
          0
          Я бы поставил двоеточие. How to: continuous integration…
            0
            действительно, спасибо!
        +1
        А я в свое время TeamCity не осилил, что-то coverage не получилось настроить, хотя Jenkins'ом сейчас доволен.
        Спасибо за топик!
          0
          а у меня обратная ситуация. дженкинс не создал файлы конфигурации там где должен (если верить документации) после установки, а разбираться стало лень. :) рад что оказался полезен!
          0

          Здесь я бы ограничил 1. Я подозреваю что в БД при одновременном запуске нескольких тестов могут начнаться конфликты данных и билды запросто не пройдут.
            +1
            Да, но я не стал углубляться в тонкости правильного конфигурирования всех параметров (тем более что у меня — я единственный коммитер, а писал я мануал со своей ситуации), портянка и так длинная. Целью было минимально настроить и поднять все компоненты чтобы автоматом собиралось и тестилось. Думаю про нюансы настройки можно накропать ещё не один такой рулон писанины…
              0
              БД для тестов лучше всего указать in-memory sqlite, это гораздо быстрее
                +1
                Мне кажется это совсем уж спорный вопрос, особенно, если учитывать, что мы тут даже виртуальное окружение проверяем на работоспособность — мало ли чего там в реализации не так. Вдруг оно будет пахать в памяти, а на планируемом мускуле не полетит? Миграции не промигрируются?

                Понятно что быстрее, но я лично от греха подальше пускаю тесты в той же СУБД что будет на боевой системе…
              0
              Для тех кому пока кажеться, что решение вроде TeamCity/Jenkins слишком громоздки и неоправданы для вашего не очень большого проекта — рекомендую посмотреть на nosy. Это автоматическая запускалка тестов через nose, которая перезапускает все тесты при измении файлов проекта. Очень удобно запускать в соседнем окне и мгновенно видеть прогрес после очередного сохранения текущего файла.
                0
                У nose очень хитрый загрузчик модулей с тестами, и в результате возможны ситуации когда стандартный ./manage.py test работает, под вебсервером работает, а nose тесты падают.

                В частности у меня была проблемы со всяческими @register декораторами, и десериализацией объектов из-за того что под nose в одном случае объект получался из project.app.module, а в другом просто app.module

                Понаступав на подобные грабли, я в итоге и написал django-jenkins, который загружает тесты в так же и запускает в том же порядке как и стандартный django test

                ИМХО, т.к формат выходных файлов одинаковый, django-jenkins можно и с Teamcity вероятно так же просто использовать.
                0
                К статье+ надо добавить в settings.py << TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'.
                Если после manage.py test не будет работать.
                  +1
                  Я указал:

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

                  Конечно, можно и в TEST_RUNNER если не хочется в INSTALLED_APPS не принципиально. Добавлю как примечание. Спасибо.
                  0
                  А если я не использую nose, а только webtest, то для передачи данных TeamCity все-равно нужен teamcity-nose?

                  Only users with full accounts can post comments. Log in, please.