Непрерывное тестирование питонопроекта

    Программист зверь ленивый, поэтому всё, что будет делаться больше одного раза надо непременно заскриптовать.

    Я уже некоторое время ковыряю TDD и задача постоянного контроля качества для меня становится всё актуальней. Особенно при пополнении команды новыми разработчиками.

    Сначала я запускал тесты руками: save, switch, $ nosetests. Потом к тестам добавились проверялки качества кода и пришлось всё засунуть в скрипт:
    pyflakes *.py
    pep8 *.py
    pylint *.py
    nosetests


    Скрипт запускать каждый раз ужасно лениво, поэтому небольшая оболочка на inotifywait стала запускать тесты и проверки после каждого сохранения:
    while true; do
    inotifywait -e modify project/*.py -qq; clear
    ./do_tests
    done


    Тут я стал более-менее доволен происходящим и даже на некоторое время расслабился. Но ведь программист кроме того, что ленив ещё и горд, поэтому результаты хочется кому-нибудь показать. Чтобы вести историю происходящего (которая очень помогает когда заходит начальник начальника и спрашивает: «ну-с, чем вы занимались последний месяц?») уже есть система контроля версий. Но она показывает только, что сделано и не даёт обзора успешности каждой ревизии. Получается что код лежит, но непонятно в каком он состоянии и что где ещё надо сделать.

    Кроме того довольно тяжело следить за коллегами, которые тоже могут что-то сделать и забыть прогнать тесты, в результате в репозитории лежит битый код, не прошедший code review и при очередном pull может внезапно начаться clusterfuck.

    И тут очень вовремя kmmbvnr@lj выпустил скринкаст, в котором он демонстрировал интеграцию тестирования для django-проектов с сабжем Jenkins (бывш. Hudson). Посмотрел я на все эти красоты, графики и отчёты и тоже захотел чтобы всё само пело и играло. Но у него django-jenkins, как и следует из названия, встраивается в джангу и генерит отчёты используя хитрую систему. Мой проект до джанги не дорос и скорее всего не дорастёт — это достаточно тривиальное WSGI-приложение, которое правда стремительно разрастается. Пришлось поднимать всё с нуля.

    Воскресенье я на это убил, но в целом всё довольно прямолинейно и теперь у меня есть симпатичные отчёты:



    Что внутри?

    0) Автоматическое получение свежих версий из репы. Система очень плотно интегрирована с меркуриалом, поэтому все правки и сообщения о коммитах там видны и доступны.

    1) Сборка проекта с нуля, как если бы это выкатывалось на боевой сервер. Создаётся окружение, выкачиваются и ставятся модули и всё такое. Сама система находится на сервере разработчиков в условиях приближеных к боевым: фря, изоляция питонских проектов, гемор с установкой.

    2) Запускаются тесты. Вместе с ними сразу происходит проверка покрытия тестами кода. В результате создаются файлы отчётов о заваленых тестах и покрытии. Зелёный график сверху — количество заваленых тестов показывается там же красным. Самый нижний график показывает покрытие. Покрытие можно посмотреть прям в файлах с подсветкой непровереных строк.

    3) Выполняется проверка качества: pep8 и pylint тщательно давят на мозги разработчику добиваясь от него порядка в коде, именах переменных и прочего. Красная ломаная, которая постоянно будет какбы намекать, что пора бы уже вынести мусор.

    4) Разработчик, закоммитивший битый код будет автоматически пожурён системой в email и jabber (лично и в MUC), а потом и ведущим програмистом, который тоже получит аналогичную жалобу. Потому что никто не ожидает испанской инквизиции!

    В итоге у нас есть система, которая берёт на себя значительную часть рутины сразу с нескольких человек.

    Дайте две!

    Сама система поставляется в виде java/war пакета. Ничего устанавливать не надо, но понадобится JRE (под фрю я поставил Diablo-1.6). На сайте можно ткнуть ссылку и сразу запустить её у себя. Запускается просто: java -jar jenkins.war. Там ещё есть опции для указания портов и всего такого. Рекомендую биндить на локалхост и завернуть в nginx. Мало ли что. От рута запускать категорически не советую.

    На фре оно у меня отказалось демонизироваться, поэтому я завернул в supervisor. Потом он ещё пригодился. Устанавливается просто — pip install supervisord.

    Все настройки производятся уже из браузера после запуска в меню Manage Jenkins.

    Первым делом надо воткнуть плагинчиков. Они качаются и ставятся сами, надо только галочки расставить.
    • Jenkins Cobertura Plugin — отчёты о test coverage
    • Hudson instant-messaging plugin — уведомления. Нужно для jabber. Ну или не нужено, если вы это не будете ставить.
    • Hudson Jabber notifier plugin — собственно жабер.
    • Hudson Mercurial plugin — интеграция с репозиторием.
    • Hudson Violations plugin — обработчик pylint

    Остальные уже установленые я отключил, ибо нефиг.

    Некоторе плагины без рестарта не появятся в настройках.

    Настройки

    Сразу стоит сделать enable security, и выбрать подходящий способ авторизации. Я взял «Project-based Matrix Authorization Strategy». Юзера создавать не надо, а вот рестартнуться надо. При повторном заходе будет предложеносразу сделать админа. Лучше его так и назвать — admin. Иначе потом можно запутаться кто есть кто, потому что оно ведёт список участников проекта глядя на отметившихся в коммитах.

    «Prevent Cross Site Request Forgery exploits» штука хорошая, но у меня с ней не заработало, пришлось не включать.

    JDK и другие «installations» настраивать не надо, всё и так работает.

    Shell executable стоит выставить в /usr/local/bash или /usr/bin/bash. Короче в полный путь к шелу, а то мало ли чем оно там будет запускать… При большом желании можно хоть питон назначить, но это неудобно.

    Jabber и Email секции вам поможет настроить Кэп и кнопка Advanced.

    Настройка проекта

    Которые тут называются Job. Указываем job name и отметку «free-style». Дальше самое вкусное, на что я и убил половину выходных.

    Билдов у меня много и делаю я их часто, поэтому показалось логичным установить лимит хранения в 10 штук / 30 дней. Если что — всегда можно нажать Build Now.

    Код я выкладываю на приватную репу в bitbucket потому что они бесплатные и довольно фичастые при этом. Git я нелюблю т.к. питонист на всю голову и у гитхаба нет бесплатных приватных реп.

    Repository URL будет такой (да, в нём надо передавать авторизацию. именно поэтому свою инсталяцию jenkins надо сразу запаролить): [code]https://юзернейм: пароль@bitbucket.org/юзернейм/uniproxy[/code]

    Repository Browser — bitbucket.

    Build triggers — poll SCM. Schedule в формате крона: [code]*/5 * * * *[/code]
    Наверно можно было бы ещё сделать trigger remotely, но мне лень. Это ж надо каждому девелоперу потом будет конфиги с этим триггером таскать… А корзинка всегда на одном месте и отовсюду доступна.

    Шаги инквизиции экзекуции

    Файлы проекта организованы следующим образом:
    .
    buildenv.sh
    pip-reqs.txt
    pylint.rc
    project/*.py
    reports/*
    venv/*

    Последние два создаются скриптом. Основные я выложил на гитхабе.

    В скринкасте я видел, что всё в одном шаге, но решил всё же разбить на три логических: build, check, test.

    Первый совсем простой: «./buildenv.sh» — тот самый, что лежит в проекте и готовит virtualenv. Можно было бы скопипастить его сюда, но это не труъ DRYъ т.к. пришлось бы держать дубликаты одного и того же.

    Второй посложнее:
    #!/usr/local/bin/bash
    venv/bin/pep8 --repeat --ignore=E501,W391 project | perl -ple 's/: ([WE]\d+)/: [$1]/' > reports/pylint.report
    venv/bin/pylint --rcfile pylint.rc project/*.py >> reports/pylint.report
    echo "pylint complete"


    Первая и последняя строки связаны с тем, что если pylint найдёт к чему придраться, то весь шаг будет считаться заваленым. А pylint хуже самой злобной училки, ВСЕГДА найдёт к чему придраться. В конфиге к нему прописал, что некоторые ошибки мне неинтересны. pep8 дополняет pylint, но больше внимания уделяет оформлению. Некоторые предупреждения отключены (длину строк проверяет pylint и там она задана в конфиге).

    Обратите внимание, что все скрипты запускаются из venv, в который были установлены на предыдущем шаге. Если там что-то развалится, всё остальное тоже порушится и на доске позора будет висеть большой красный шар.

    Третий запускает тесты и собирает покрытие:
    venv/bin/coverage run --include 'project/*.py' project/tests.py --with-xunit --xunit-file=reports/tests.xml --where=project
    venv/bin/coverage xml -o reports/coverage.xml


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

    Отчёты

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

    Включаем «Publish JUnit test result report» и, хотя они никакие не JUnit, а nosetests указываем **/reports/tests.xml, что значит «от корня репы/задания в каталоге reports.

    Следующим пунктом идёт «Report Violations» и там pylint. Несмотря на то, что в названии поля стоит «XML filename pattern», а pylint выдаёт никакой не xml, так же указываем файл **/reports/pylint.report где они на пару с pep8 всячески критикуют код неряшливых разработчиков с занесением в личное дело. Это очень помогает утром включиться в работу: пришёл, глянул график violations и пока исправлял, хоть вспомнил что вчера писал.

    Ну и последнее. «Publish Cobertura Coverage Report» лежащий в «**/reports/coverage.xml». Я понятия не имею что такое Cobertura, но формат питонского coverage оно понимает исправно.

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

    Кодер птица гордая — не пнёшь, не полетит

    В самом конце идут уведомления по email и jabber, можно настроить по вкусу. Единственное, что их надо сначала настроить в основном конфиге сервера интеграции, а то оно работать не будет.

    Поехали и махнул рукой

    Настройки записаны, можно отправить на сборку. Либо вручную через кнопку Build Now, либо закоммитив и запушив что-нибудь в репу. Внизу сайдбара, в Build History появится новая задача и бегунок прогресса, кликнув на который можно перейти в раздел «Console Output» текущего билда и смотреть за ним в прямом эфире. Когда всё будет окончено, в Status появится всякая всячина по которой можно полазить и посмотреть как что где.

    Bonus!

    Каждый раз воссоздавая venv оно качает все модули с pypi и делает это очень долго. Кроме того это трафик. Немного поискав я нашёл в pypi collective.eggproxy, кэширующий прокси-репозиторий мимикрирующий под pypi.python.org/simple. Запускается просто как `eggproxy_run`. У него нет справки и по-умолчанию он складывает всё в /var/www, что не есть хорошо. Почитав его доки на сайте можно узнать как сделать файл конфига, чтобы настроить пути и порты. Оно тоже не захотело демонизироваться, поэтому вслед за Jenkins было отправлено в объятия supervisord.

    buildenv.sh уже натренирован адаптироваться к наличию/отстутствию этой прокси, там всё просто.

    Credits

    kmmbvnr@lj: Как начать тестировать и получать от этого удовольствие. Кроме интеграции с Jenkins описывается ещё django-any. Всем джангистам настоятельно рекомендую ознакомиться и пользоваться.

    Setting up a python CI server with Hudson, всё ещё актуальный пост с правильными советами, на основе которого сделана эта моя интерпретация.

    Ещё для тестирования проекта была задействована простая обёртка над WSGI приложениями, позволяющая без лишнего гемороя заниматься тестированием и отладкой без wsgi-контейнеров и ручной работы в браузере.
    Метки:
    Поделиться публикацией
    Комментарии 20
    • +1
      pyflakes *.py
      pep8 *.py
      pylint *.py

      Вот про это было б клёво статью почитать
      • 0
        А что там читать? `pip install pyflakes pep8 pylint` и поехали. Читается вывод, лишние коды ошибок дописываются в опции.
        • 0
          А сам не знаю. Ну, может, какие-то нюансы осветить, рассказать, что полезного в конфиги насувать. Как тестировать целиком джанго-проект. Почему нужны все три тулзы и не хватает одной.
          • +4
            Я немножко на эту тему написал — ничего, что самопиар?
            • +1
              В django-jenkins включает конфиг pilint'а в котором выключенны самые раздражающие варнинги в django проекте. Для pylint cделана отдельная команда, так что можно запускать с консоли ./manage.py pylint

              Но все равно достаточно много false-positive сообщений. Я сейчас думаю попробовать перейти на связку pep8+pyflakes
              • 0
                Т.е. их вообще нереально вычистить? Все false-positive сообщения? Зачем отказываться от pylint? Может быть юзать pep8+pyflakes вместе с pylint вместе? Или pep8+pyflakes покрывает возможности pylint? Я ещё про какой-то pychecker слышал, который исполняет python-код.
                • 0
                  >> их вообще нереально вычистить?
                  Мне кажется тогда в pylint останется только то же что и pep8+pyflakes

                  Можешь посмотреть на результат pylint на django-jenkins. Имхо большинство варнингов не очень полезны.
                  • 0
                    Для этого у jenkins есть границы значений. Типа, что до 10 violations — это ок.
          • +2
            я бы хотел работать в вашей команде:)
            • 0
              Шикарно! Большое спасибо.
              • 0
                Для схожих целей используем buildbot, правда без графиков, зато сам билдбот на питоне, все шаги сборки\тестировки тоже пишутся на питоне :)
                • –10
                  Неделя пытона на хабре?
                  • 0
                    supervisor — хорошая штука
                    • 0
                      А на этот скринкаст можно взглянуть где-то не на narod.ru? Хотелось бы прямо в онлайне его посмотреть
                    • 0
                      А вот кто бы ещё подсказал, как убрать raise NotImplementedError из «ошибок» coverage…
                      • +1
                        у coverage, программно по крайней мере, можно настраивать регексп игнорируемых строк.

                        Но обычно его используют для комментариев типа #no-cover
                      • 0
                        >>Потому что никто не ожидает испанской инквизиции!
                        Our chief weapons are: surprise… Fear and surprise!

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