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

суббота, 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, а вы?

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

18 комментариев:

  1. Даже и не знал о существовании такого фреймворка. Спасибо за наводку =)

    ОтветитьУдалить
  2. Честно говоря, с чисто прагматичной точки зрения, я не встречал более удобной библиотеки.

    ОтветитьУдалить
  3. А чем Google C++ Testing Framework отличается от CppUnit?

    ОтветитьУдалить
  4. >> Здорово, что данная фича основана на моем коде.

    Забавно - я тоже делал такой же патч :) Выбрали твой?

    ОтветитьУдалить
  5. White Knight: Навскидку, при использовании CppUnit мне приходилось каждый тест прописывать в головном .h файле как функцию-член test fixture. Что очень раздражало. Может сейчас уже этого нет, я не в курсе. В GTest просто пишешь тест через TEST() или TEST_F() и все. Удобно при перетаскивании тестов из модуля в модуль.

    А еще в GTest очень удобно печатать трассировочные данные при сбое теста, так как в любую функцию-assert можно писать как в поток через <<. И это будет печататься, если assert сбоит, например:

    EXPECT_TRUE(some_object.some_function()) << "Блин! тут сбой, а some_object.i = " << some_object.i;

    и т.д.

    ОтветитьУдалить
  6. Как сделать так, чтоб русский текст правильно отображался одновременно в консоле и XML отчете?

    ОтветитьУдалить
  7. Хм, удивляюсь, почему там вообще используется русский текст. А в какой кодировке русский в XMLe? Может изменить кодировку консоли сообразно?

    ОтветитьУдалить
  8. Ну русский текст я сам использую в отчете для удобства. Проект юникодный, консоль - всегда OEM, к сожалению. А ф-ции типа wprintf работают только за счёт преобразований. Удалось добиться желаемого путём настройки консоли и xml файла (просто заменив encoding=) на кодировку 1251. Но с версией 1.4.0 в xml файле русский текст больше не отображается таким способом. Видимо это была ошибка, которую уже исправили.

    ОтветитьУдалить
  9. В общем пришлось убрать преобразование в UTF-8

    ОтветитьУдалить
  10. А вы напишите подробнее про Google C++ Mocking Framework ?

    ОтветитьУдалить
  11. Как в GTF можно подключить систему тестирования к разработке отдельных библиотек (*.dll) ?

    ОтветитьУдалить
  12. Фреймворк хороший, вот только не получается заставить его работать на AIX 5.2. После небольших изменений скомпилировал, но работать он не хочет...

    ОтветитьУдалить
  13. K01egA: Какие именно проблемы? Я использую это на Linux, Solaris Sparc, HP-UX v3, AIX 5.2, 5.3 and 6.0. Вроде проблемы были только на старом компиляторе HP-UX v2. На v3 все хорошо.

    ОтветитьУдалить
  14. компилятор VisualAge C++ 6, для начала пропатчил gtest-port.h аналогично http://groups.google.com/group/googletestframework/msg/9431cd036422ef99 для версии 1.3, потом компилятор отказывался понимать
    template typename T
    static void Delete(T * x) {
    delete x;
    }
    заменил на
    template typename T
    struct Delete {
    void operator()(T * x)
    {
    delete x;
    }
    };
    скопмилиовал, но любой тест падает в кору из-за того что GTEST_FLAG(internal_run_death_test) не инициализируется пустой строкой, GTEST_FLAG(internal_run_death_test) == NULL.
    Все глобальные переменные которые инициализируются функцией или конструктором имеют нулевое значение, функция инициализации похоже вообще не вызывается.
    Наверное никто не пользуется уже такими древними компиляторами, но политика партии не позволяет использовать другой.

    ОтветитьУдалить
  15. У меня на AIX стоит:

    $ xlc -qversion
    IBM XL C/C++ Enterprise Edition V8.0 for AIX
    Version: 08.00.0000.0020

    А на AIX 6.1 -- v10.1.

    Так что про шестерку ничего не скажу. У AIX вообще полно приколов со статическими объектами, но у меня как-то все работает. Может шестерка дествительно может чего-то не понимать.

    ОтветитьУдалить
  16. Что ж прийдется использовать CppUnit он хоть и не такой удобный но работает без проблем.

    ОтветитьУдалить