*** ВНИМАНИЕ: Блог переехал на другой адрес - demin.ws ***
Показаны сообщения с ярлыком hudson. Показать все сообщения
Показаны сообщения с ярлыком hudson. Показать все сообщения

суббота, 3 октября 2009 г.

Google C++ Testing Framework 1.4.0

Вчера гугловцы анонсировали новую версию моей любимой библиотеки тестирования Google C++ Testing Framework - 1.4.0.

Что новенького?

Одна из главных новых возможностей - это "The event listener API". А попросту говоря, возможность полностью контролировать процесс печати результатов тестирования. Это позволяет формировать отчеты по тестированию в нужном формате без изменения кода библиотеки.

Например, стандартный вывод при выполнении элементарного теста (файл runner.cpp):
#include "gtest/gtest.h"

TEST(One, Simple) {
EXPECT_EQ(1, 2);
}

int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
будет таким:
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from One
[ RUN ] One.Simple
runner.cpp(4): error: Value of: 2
Expected: 1
[ FAILED ] One.Simple (15 ms)
[----------] 1 test from One (15 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (31 ms total)
[ PASSED ] 0 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] One.Simple

1 FAILED TEST
Для задания иного формата вывода нужно реализовать свой event listener (назовем его сервис печати). Например (файл runner.cpp):
#include "gtest/gtest.h"

using namespace ::testing;

// Данный класс заменит стандартный сервис печати.
class LaconicPrinter : public ::testing::EmptyTestEventListener {
// Вызывается до начала работы теста.
virtual void OnTestStart(const TestInfo& test_info) {
printf("*** Test %s.%s starting.\n",
test_info.test_case_name(), test_info.name());
}

// Вызывается при срабатывании какого-либо утверждения или явного вызова
// функции SUCCESS().
virtual void OnTestPartResult(const TestPartResult& test_part_result) {
printf("%s in %s:%d\n%s\n",
test_part_result.failed() ? "*** Failure" : "Success",
test_part_result.file_name(),
test_part_result.line_number(),
test_part_result.summary());
}

// Вызывается после выполнения теста.
virtual void OnTestEnd(const TestInfo& test_info) {
printf("*** Test %s.%s ending.\n",
test_info.test_case_name(), test_info.name());
}
};

TEST(One, Simple) {
EXPECT_EQ(1, 2);
}

int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);

// Получаем ссылку на список сервисов печати.
::testing::TestEventListeners& listeners =
::testing::UnitTest::GetInstance()->listeners();

// Удаляем стандартный сервис печати.
delete listeners.Release(listeners.default_result_printer());
// Добавляем наш сервис в список. Google Test самостоятельно удалит этот объект.
listeners.Append(new LaconicPrinter);

return RUN_ALL_TESTS();
}
Теперь отчет по работе тестов будет выглядеть так:
*** Test One.Simple starting.
*** Failure in runner.cpp:31
Value of: 2
Expected: 1
*** Test One.Simple ending.
Необходимо отметить, что одновременно может быть зарегистрировано несколько сервисов печати. Но в этом случае их выводы могут смешиваться и превращаться в кашу. Для избежания этого мы принудительно удаляем стандартный сервис печати, чтобы его вывод не мешал нашему.

Полностью интерфейс сервиса печати выглядит так:
class EmptyTestEventListener : public TestEventListener {
public:
// Вызывается при начале прогона всех тестов.
virtual void OnTestProgramStart(const UnitTest& unit_test);
// Вызывается при начале очередной итерации тестирования. Google Test
// позволяет управлять количеством итерации при прогоне тестов.
virtual void OnTestIterationStart(const UnitTest& unit_test, int iteration);
// Вызывается до функции Environment::SetUp(), устанавливающей необходимое
// окружение для работы всех тестов.
virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test);
// Вызывается после функции Environment::SetUp(), устанавливающей необходимое
// окружение для работы всех тестов.
virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test);
// Вызывается при начале прогона группы тестов (у которых первый параметр
// макроса TEST/TEST_F одинаковый).
virtual void OnTestCaseStart(const TestCase& test_case);
// Вызывается при начале работы теста.
virtual void OnTestStart(const TestInfo& test_info);
// Вызывается при срабатывании утверждения в тесте или явного вызова
// функции SUCCESS().
virtual void OnTestPartResult(const TestPartResult& test_part_result);
// Вызывается после завершения работы теста.
virtual void OnTestEnd(const TestInfo& test_info);
// Вызывается после прогона группы тестов.
virtual void OnTestCaseEnd(const TestCase& test_case);
// Вызывается до функции Environment::TearDown(), производящей освобождение
// ресурсов, занятых Environment::StartUp().
virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test);
// Вызывается после функции Environment::TearDown().
virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test);
// Вызывается после очередной итерации тестирования.
virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration);
// Вызывается после прогона всех тестов.
virtual void OnTestProgramEnd(const UnitTest& unit_test);
};
Также из значимого можно отметить новый ключ командной строки --gtest_shuffle, позволяющий запускать тесты в случайном порядке. Ключом --gtest_random_seed=SEED можно "управлять" случайностью этого порядка. Если SEED равен 0, то случайность будет действительно случайной, так как случайная последовательность будет определяться текущим временем.

Что приятно, теперь формат XML файлов, генерируемых при использовании ключа --gtest_output, полностью совместим с форматом JUnit. Это значит, что, например, система автоматической сборки, тестирования и интеграции Hudson теперь понимает отчеты Google Test без дополнительный конвертации.

Также теперь при работе в Visual Studio сообщения о сбойных тестах выводятся прямо в окно "Output", что позволяет, кликая на них, сразу находить строки, где сбоят тесты. Здорово, что данная фича основана на моем коде.

Еще, теперь время работы тестов печатается всегда, по умолчанию, то есть опция --gtest_print_time будто бы всегда включена.

Есть еще несколько незначительных улучшений:
  • поддержка CodeGear Studio
  • собственная реализация tuple для независимости от boost при использовании Combine()
  • множество улучшений для совместимости с Solaris, Windows Mobile и некоторыми другими платформами.
Радость, одно слово.

Я перестал что-то писать что-либо на C++ без тестов, а Google Test делает этот процесс простым и легким.

Я уже обновился до версии 1.4.0, а вы?

Ссылки по теме:

воскресенье, 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’ов сделанных в этот день. И только один из них был в сетевой подсистеме. Автор сего “улучшения” тоже нашелся сразу. Оказывается, человек что-то там оптимизировал в целях ускорения, а вышло наоборот.  Итого, около часа на полное разбирательство в проблеме, включая перебрасыванием электропочтой с участниками инцидента. Я думаю, ручной поиск занял был день-два. 

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

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

 

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