*** ВНИМАНИЕ: Блог переехал на другой адрес - demin.ws ***

воскресенье, 26 апреля 2009 г.

Система автоматизированной интеграции Hudson

Не секрет, что максимально автоматизированная "ночная" интеграция (сборка, тестирование, архивация) — это залог уверенности в том, что можно в любой момент выдать работоспособный релиз. А это одна из основ гибкого подхода к разработке.

Мы использует для этих нужд Hudson. Написано на Java, управляется через веб-интерфейс.

Несмотря на мою нелюбовь к Java, лично я мирно ужился с этой программой.

Что умеет Hudson, и для чего он нужен?

Hudson — это программа, которая умеет запускать скрипты локально или на удаленных машинах в зависимости от различных условий. Скрипты обычно производят сборку, тестирование, генерацию отчетов и документации, а события — это в основном поступление новых изменений в систему контроля версий.

Для начала, Hudson построен на плагинах, коих много, под разные запросы и желания.

Первый плагин, который нужен был конкретно нам — это плагин для работы с Perforce. Он умеет периодически опрашивать Perforce о наличии новых commit’ов и инициировать некоторые события, например сборку или запуск тестов.

Далее, ключевой момент для нас. Hudson имеет master-slave архитектуру, что позволяет с одного головного компьютера (master) проводить компиляцию, тестирование, архивирование и т.д. на множестве slave-машин. Так как наш софт поддерживается на нескольких разных типах UNIX и на Windows, по подобный подход очень упрощает управление таким зоопарком сборочных машин. Время разворачивания очередного slave’а — несколько минут (копирование slave.jar, запуск “java –jar slave.jar” и прописывание адреса этого slave’а на головной машине).

Еще мы активно используем плагины для посылки извещений по электропочте и для наглядного отображения результатов тестирования из формата jUnit. Наш софт состоит из смеси С, С++ и Java, поэтому пришлось выбрать единый формат представления журналов тестирования. Остановились на формате jUnit.

Каждая задача в Hudson может выполняться как на самом мастере, так и на заданном множестве slave-машин. Также  задача может являться условием запуска другой задачи. Например, если задача компиляции проекта прошла успешно, то инициируется задача тестирования. Естественно, на каждом этапе можно архивировать промежуточные результаты (логи, тестовые файлы и т.д.), к которым можно всегда вернуться позже.

Наш сценарий использования Hudson таков: каждые пять минут Hudson опрашивает Perforce. Если в какой-то ветке появились новые изменения, то запускается “чистая” сборка ветки с новыми кусками кода. Каждая такая сборка снабжается файлом, в котором перечислены изменения по сравнению с предыдущей сборкой (changelog). Если сборка прошла успешно, по запускается набор функциональных и приемочных тестов. Кроме этого каждую ночь делается сборка со всеми изменениями за день. Если тесты прошли успешно, что результат архивируется в виде очередного ночного билда и выкладывается на ftp.

Если какая-то задача оканчивается сбоем (например, компиляция, так как кто-то “сломал” сборку, забыв проверить новый код на другой платформе, или какой-то из тестов не работает), то посылаются извещения ответственным лицам и также виновнику сбоя.

При нашей интенсивности commit’ов крайне редко за пять минут появляются более одного. Чем это удобно? А тем, что если при очередной сборке кто-то сломал функциональный или приемочный тест, сразу выясняется кто и как это сделал. 

В целом, Hudson позволил нам сделать такую универсальную консоль в виде веб-странички, на которой в одном месте сразу видно все, что происходит в разработке, начиная от состояние функциональных и приемочных тестов, отчетов по покрытию и анализу качества кода и заканчивая списком незакрытых инцидентов в каждой ветке продукта.

А теперь реальный пример. В какой-то момент бета-тестеры начали сообщать, что система стала “как-то тормозить”. Точного момента никто не засек, а последняя "нормальная быстрая” сборка, которую эти тестеры имели, была сделана месяц назад. За этот месяц в ветку было внесено полсотни commit’ов. Искать среди них проблемный было бы занятием скучным (откат на определенную ревизию, сборка, тестирование, сравнение и т.д.).

Меня выручил Hudson. Я просмотрел отчеты по прогонам функциональных тестов за этот месяц и буквально сразу обнаружил, что в определенный день тесты сетевой подсистемы стали работать заметно медленнее. Область поиска сразу сузилась до четырех commit’ов сделанных в этот день. И только один из них был в сетевой подсистеме. Автор сего “улучшения” тоже нашелся сразу. Оказывается, человек что-то там оптимизировал в целях ускорения, а вышло наоборот.  Итого, около часа на полное разбирательство в проблеме, включая перебрасыванием электропочтой с участниками инцидента. Я думаю, ручной поиск занял был день-два. 

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

А вы какими программами пользуетесь для автоматической интеграции?

 

Посты по теме:

понедельник, 13 апреля 2009 г.

Задержка в одну секунду через time()

Иногда требуется сделать в программе цикл, работающий кратное секундам время. Есть множество способов для это.

Я предложу, как мне кажется, очень простой и очень переносимый способ.

Стандартная функция time() возвращает так называемое UNIX-время в секундах. Проблема в том, что секунда, номер которой возвращает эта функция, может быть уже через пару микросекунд перейдет на следующую. Надо как-то "подравняться" к границе секунд.

Фрагмент кода, в котором рабочий цикл имеет условие, позволяющее ему работать время, близкое к одной секунде:

...
// Получаем номер текущей секунды
time_t started = time(NULL);
// Ждем перехода на следующую секунду
while (time(NULL) == started);
// И сразу запускаем рабочий цикл
started = time(NULL);
do {
// Цикл, работающий в течение секунды
...
} while (time(NULL) == started);
...

Тут, конечно, есть недостатки. Подготовительный цикл ожидания перехода на следующую секунду может "есть" процессорное время, если time() для вашей системы не отдает time slice. Также сложно сделать какой-то надежный универсальный шаблон или макрос, так как надо гарантированно избежать какого-либо лишнего кода, чтобы не терять точность.

В целом, такой прием дает рабочему циклу работать время, очень близкое к секунде.

Если знаете, как сделать еще проще — предлагайте.


Посты по теме:

пятница, 3 апреля 2009 г.

Анализатор покрытия кода тестами Bullseye Coverage

Статические анализаторы кода, например Coverity или Klockwork являются отличным подспорьем для качественного программирования.

Еще одним мощнейшим подспорьем является тестирование. Есть различные виды тестирования — unit-тестирование, функциональное тестирование, регрессивное тестирование и т.д.

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

Одним из таких показателей может является количество строк кода, которые были отработаны (выполнены) в процессе тестирования. Эдакая мера для черных дыр в коде, которые никогда не выполняются обычно, а когда таки до них доходит, то все падает. Этот подход вовсе не отменяет функциональное тестирование, а органично дополняется его.

Итак, задача — надо понять, какие части программного коды были задействованы (были выполнены хотя бы раз) в процессе тестирования.

Представим ситуацию, что тестерам дали задание написать функциональные тесты для новой версии API на основе unit-тестов, написанных программистами, и на основе ожиданий заказчика от этого API. Они написали. А как понять, насколько полно они задействовали своими тестами все укромные уголки кода? Нужен какой-то инструмент.

Мы в компании остановились на Bullseye Coverage. Относительно небольшая цена (для сравнения с Coverity, которая стоила нам несколько десятков кило-зеленых на год, хотя это того стоит). Можно получить тестовый временный ключ для того, чтобы поиграться перед покупкой. Система поддерживает множество основных платформ.

Bullseye Coverage работает на уровне компилятора. Все что нужно — это активировать ее перед компиляцией проекта. После этого бинарные модули проекта будут сохранять в специальном файле статистику по собственной работе (чем-то похоже на работу профилировщика). Откомпилировали, запустили тесты (любые) и посмотрели — какие строки кода были реально выполнены этими тестами.

Bullseye Coverage может показывать задействование на уровне файлов/модулей, функций/классов и просто строк. Открыв файл исходного текста после прогона тестов специальным просмотрщиком можно, например, сказать, что эта конкретная строка или эта функция никогда не вызывалась в процессе тестирования. Порой это очень впечатляет.

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

Лично меня результаты анализа некоторых наших проектов очень впечатлили и, порой, озадачили.

А вас?


Посты по теме: