RReverser's

Ingvar Stepanyan

JavaScript developer, speaker and reverse engineer. D2D programmer. Sometimes human.


Continuous Integration: установка и настройка Hudson+JUnit

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

Этой статьей я хотел бы упрощенно рассказать о небольшой авантюре, которой на днях пришлось заниматься и более-менее подробно описать, как проще пройти через весь этот процесс без наступания на грабли, с которыми приходилось сталкиваться на протяжении тех двух ночей самому. А именно, речь будет идти о настройке системы непрерывной интеграции.

Continuous Integration is a software development practice where members of a team integrate their work frequently, usually each person integrates at least daily — leading to multiple integrations per day. Each integration is verified by an automated build (including test) to detect integration errors as quickly as possible. Many teams find that this approach leads to significantly reduced integration problems and allows a team to develop cohesive software more rapidly.

…taken from Martin Fowler 's article on CI

Как обычно проходит build & deployment продукта? Он состоит из следующих этапов:

  • Компилирование;
  • Тестирование;
  • Сборка;
  • Копирование на удаленный сервер;
  • Запуск.

При «ручном» исполнении все эти действия представляют собой довольно рутинный и длительный процесс. Собственно возникает вопрос — а не могли бы мы его как-то автоматизировать?

Для чего? Во-первых. На графике, приведенном выше (он мне очень понравился и я его стырил с http://www.agitar.com/solutions/why_unit_testing.html ), показана зависимость количества багов, которые представлены в тот или иной момент времени, количество найденных на данный момент багов и стоимость их устранения на данном этапе. Фактически, как бы это ни было "странно", но чем раньше мы находим баги, тем проще и дешевле их будет исправить. И здесь на помощь приходит автоматическое модульное тестирование.

Его целью является изоляция каждой части программы и убеждение в том, что каждая отдельная часть является корректной. Модульный тест обеспечивает жесткий «контракт», по которому должен работать тестируемый код. Как результат, это дает некоторые преимущества. Модульное тестирование позволяет программисту, когда он будет изменять код (проводить рефакторинг) быть уверенным, что модуль работает правильно (это — регрессивное тестирование). Поскольку модульное тестирование требует написания тестов для всех функций и методов в программе, ошибки быстро локализуются и исправляются. Ну и что самое интересное для нас — модульное тестирование может быть применено в интеграционном тестировании: тестирование отдельных модулей и совокупности этих модулей делает интеграционное тестирование значительно легче.

Хорошо. Представим, что программисты таки использовали автоматические тесты для тестирования разрабатываемых ими модулей. Но все еще остается нерешенной другая проблема: им необходимо как-то сливать свой код в кучу. Когда количество изменений невелико и они не пересекаются, это все без проблем решается вручную. Однако когда речь идет о десятках файлов, причем в любой момент может возникнуть необходимость вернуть изменения по некоторым из них, этот процесс значительно усложняется и становится довольно продолжительным. Здесь на помощь приходят системы контроля версий. Они решают вышеуказанные проблемы и позволяют:

  • Хранение версий файлов, причем обычно сохраняются только изменения между предыдущей и текущей версией и таким образом хранилище не растет слишком быстро;
  • Возможность получить любые предыдущие версии сохраненных файлов;
  • Просмотр изменений внесенных между заданными в запросе версиями;
  • Хранение и просмотр комментариев и авторов относительно внесенных изменений.

Для того, чтобы объединить этот весь процесс в единое целое, и появилась такая практика разработки ПО как непрерывная интеграция. В обычном проекте, где над различными частями системы разработчики работают независимо, стадия интеграции является завершающей и представляет собой довольно болезненный процесс. Она может непредсказуемо задержать окончание работ при наличии в разных разработчиков несовместимого кода, который соответственно не может автоматически cлиться и из-за непредвиденных при написании модулей ситуаций, которые проявляют себя только при тестировании всего проекта. Переход к непрерывной интеграции позволяет снизить трудоемкость интеграции и сделать ее более предсказуемой за счет наиболее раннего выявления и устранения ошибок и противоречий.

Давайте рассмотрим как работает непрерывная интеграция на простом примере. Пусть, мы разрабатываем библиотеку на языке Java для операций над комплексным числами Complex_Stepanyan . В нашей системе будем использовать следующие инструменты:

  • Language : Java (если еще нету JDK, то устанавливаем его с этой страницы ).
  • IDE : NetBeans — здесь, думаю, объяснений не надо, это одна из самых популярных IDE для разработки на Java с широкими возможностями и имеющая среди бесплатных конкурентов разве что Eclipse. Но это больше дело вкуса. Если что - скачивать здесь .
  • Testing engine : JUnit — библиотека для тестирования программного обеспечения для языка Java. Созданный Кентом Беком и Эриком Гаммой, JUnit является представителем семьи фреймворков xUnit для различных языков программирования, которая берет начало в SUnit Кента Бека для Smalltalk. Сами средства тестирования встроены в NetBeans.
  • CVS : Mercurial — свободная распределенная система управлением версий файлов и совместной работы. Является единой программой (hg), написанной на скриптовом языке Python и C. Ее преимущества:
    • независимая от объема кода;
    • высокое быстродействие;
    • компактное хранение данных в проиндексированом и сжатом виде;
    • распределенная модель разработки, допускающая произвольное слияние отдельных децентрализованных репозиториев;
    • встроенные средства резервного копирования и проверки целостности;
    • привычный CVS-подобный набор команд;
    • большой выбор GUI- и web-интерфейсов;
    • поддержка нескольких моделей организации репозитория: централизованная cvs-подобная, распределенная полу-иерархическая и иерархическая.
  • CI Server : Hudson — мощный и широко используемый сервер непрерывной интеграции с открытым кодом. Фактически, его конкурентом является CruiseControl, но Hudson значительно проще для установки и настройки, имеет удобный визуальный интерфейс и не требует особых знаний для настройки дополнительных задач благодаря широкой базе расширений. Скачиваем с официального сайта проекта .

Вернемся к нашему мини-проекту. Прежде всего, установим необходимые инструменты.

Для начала устанавливаем NetBeans. Для сборки мы можем использовать Ant, который идет в стандартной поставке NetBeans. Нам лишь останется прописать путь к его бинарникам в системную переменную PATH. Туда же прописываем путь к бинарникам Java Developer Kit.

Для управления Mercurial-репозиторием установим TortoiseHg (скачать можно Download" href="http://tortoisehg.bitbucket.org/download/">Download"; href="http://tortoisehg.bitbucket.org/download/">Download"; href="http://tortoisehg.bitbucket.org/download/">Download"; href="http://tortoisehg.bitbucket.org/download/">тут ). Это утилита из семейства популярных Windows-утилит для полного контроля над репозиториями в удобном интерфейсе.

Инсталлятор Hudson представляет собой единый для различных систем WAR-пакет, который мы устанавливаем командой java -jar hudson.war . Он при этом он сам распаковывается в подпапку .hudson пользовательской директории и запускается.

Можно было бы на этом остановиться и уже использовать систему, но для удобства стоит настроить автоматический запуск. Под Windows для этого можно выбрать пункт Install as Windows Service и на этом шаге можем указать собственную директорию для установки Hudson.

NetBeans имеет встроенные инструменты для управления Hudson-серверами.

Добавим наш сервер.

Откроем наш проект в NetBeans и выберем Versioning -> Initialize Mercurial Project . NetBeans сам найдет путь к установленной утилиты hg и создаст с ее помощью репозиторий в папке проекта. Из своих настроек добавим игнорирование папок build и dist, чтобы они не заливались на сервер.

Добавляем наш проект в Hudson средствами NetBeans. Выберем генерацию JavaDoc и тестирования. NetBeans скопирует используемые библиотеки (в частности, JUnit) и сгенерирует конфигурационные файлы для сборки проекта.


Здесь один очень важный момент : для того, чтобы Ant мог впредь собирать проект сам, необходимо вручную указать в свойствах проекта, чтобы при сборке использовались эти скопированные библиотеки, а не оригинальные.

Все, проект создан и мы можем его увидеть в web-интерфейсе Hudson и в его папке.

Теперь клонируем оригинальный репозиторий (конечно, в идеале два репозитория находятся на разных машинах, но в нашем упрощенном варианте они находятся на одном компьютере).

Далее было решено автоматизировать построение проекта на пост-комит. Добавим хуки в .hg/hgrc : в нашей копии проекта на commit ставим автоматический push на сервер, а в серверном workspace ставим автоматический update репозитория и вызов build на Hudson. Таким образом, у нас будет каждый раз происходить цепочка событий client-commit => client-push => server-pull => server-update => server-call-hudson-build

Все, базовая настройка проекта завершена.

Попробуем сделать коммит с заведомо заваленным тестом.

Соответственно видим на сервере сообщение об UNSTABLE BUILD и можем выяснить, какие именно из тестов не прошли.


Теперь попробуем залить версию уже с исправленной ошибкой.

Видим, что компиляция и тестирование прошли успешно, и мы можем скачать сгенерированный JAR-дистрибутив и посмотреть JavaDoc-документацию.


К сожалению, в рамках одной статьи невозможно описать все что хотелось бы и стоило, поэтому ее стоит рассматривать чисто как мануал-толчок для дальнейших исследований и размышлений. Если возникнут вопросы/замечания - пишите мне или моим хорошим знакомым Google и StackOverflow :)

comments powered by Disqus