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

четверг, 19 марта 2009 г.

Google Test Framework 1.3.0

Сегодня вышла новая версия Google Test Framework1.3.0.

Радостно, что авторы воплотили мою идею, когда вся библиотека собирается всего в два файла: gtest-all.cc и gtest.h. Теперь для этого есть специальный скрипт на Питоне. Распаковываем архив gtest-1.30.zip и запускаем:

python scripts\fuse_gtest_files.py . fuse
После этого во вновь созданном подкаталоге fuse будет находиться "упакованная" версия библиотеки в виде двух файлов gtest/gtest-all.cc и gtest/gtest.h. Моя аналогичная, но ручная сборка для предыдущей версии больше неактуальна.

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

#include "gtest/gtest.h"
int main(int argc, char* argv[]) {
testing::InitGoogleTest(&argc, argv);
testing::GTEST_FLAG(print_time) = true;
return RUN_ALL_TESTS();
}
Итак, новые возможности версии 1.3.0:
  • поддержка так называемых смертельных тестов для Windows (раньше это работало только под Linux)
  • параметр командной строки --gtest_also_run_disabled_tests для принудительного запуска отключенных тестов
  • возможность распараллеливать запуск тестов на разных машинах
Небольшая программа ниже демонстрируем новые "вкусности" Google Test.

Файл runner.cpp:
#include "gtest/gtest.h"

#include <fstream>
#include <iostream>
#include <cstdlib>

// -------------------------------------------------------

// Данная функция, если файл не существует, печатает сообщение
// об ошибке и завершает программу с ненулевым кодом.
void openfile(const char* name) {
std::ifstream is(name);
if (!is) {
std::cerr << "Unable to open the file" << std::endl;
std::exit(1);
}
}

// Тест для функции openfile().
TEST(OpenFileDeathTest, ExitIfNoFile) {
// Задаем заведомо несуществующий файл и смотрим - завершилась
// ли программа с ненулевым кодом. Также проверяем регулярным
// выражением то, что программа напечатала при выходе.
// Мы ожидаем слово "open" среди остального вывода.
ASSERT_DEATH({ openfile("__nofile__"); }, ".*open.*");
}

// -------------------------------------------------------

// Данная функция должна падать с assert'ом, если делитель
// равен нулю.
int divide(int a, int b) {
assert(b != 0);
return a / b;
}

// Тест для assert'а в функции divide().
TEST(AssertDeathTest, DivideByZero) {
// Задаем нулевой делитель и смотрим - упала или нет.
// Вывод программы при падении не проверяем.
ASSERT_DEATH({ divide(1, 0); }, "");
}

// -------------------------------------------------------

// Данная функция должна при ненулевом коде завершать
// программу, прибавив к заданному коду ошибки 50.
void abandon(int code) {
if (code != 0) std::exit(code + 50);
}

// Тест для функции abandon().
TEST(AbandonDeathTest, ExitCode) {
// Вызываем функцию и смотрим код возврата.
// Вывод программы при выходе не проверяем.
ASSERT_EXIT(abandon(200), testing::ExitedWithCode(250), "");
}

// -------------------------------------------------------

// Заведомо неработающий “сломанный” тест.
// Если имя группы тестов или теста в отдельности предварить
// словом DISABLED_, то тест не будет участвовать с запуске.
// Это удобно, когда какой-то тест сломан, времени на его
// отладку нет, но убирать его из тестирования совсем нельзя.
// В это случае его можно отключить. Google Test при каждом
// запуске будет напоминать, сколько имеется отключенных тестов.
// В процессе же работы над тестом можно запускать программу
// с параметром "--gtest_also_run_disabled_tests", который
// будет проверять также и отключенные тесты.
TEST(BadTest, DISABLED_Test) {
FAIL();
}

// -------------------------------------------------------

int main(int argc, char* argv[]) {
testing::InitGoogleTest(&argc, argv);
// Принудительно печатаем время работы тестов.
testing::GTEST_FLAG(print_time) = true;
return RUN_ALL_TESTS();
}
Компилируем в Visual Studio:
cl /EHsc /I. /Ferunner_vs2008.exe /DWIN32 runner.cpp gtest\gtest-all.cc
Запускаем:
[==========] Running 3 tests from 3 test cases.
[----------] Global test environment set-up.
[----------] 1 test from OpenFileDeathTest
[ RUN ] OpenFileDeathTest.ExitIfNoFile
[ OK ] OpenFileDeathTest.ExitIfNoFile (31 ms)
[----------] 1 test from OpenFileDeathTest (31 ms total)

[----------] 1 test from AssertDeathTest
[ RUN ] AssertDeathTest.DivideByZero
[ OK ] AssertDeathTest.DivideByZero (31 ms)
[----------] 1 test from AssertDeathTest (31 ms total)

[----------] 1 test from AbandonDeathTest
[ RUN ] AbandonDeathTest.ExitCode
[ OK ] AbandonDeathTest.ExitCode (32 ms)
[----------] 1 test from AbandonDeathTest (32 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 3 test cases ran. (94 ms total)
[ PASSED ] 3 tests.

YOU HAVE 1 DISABLED TEST
Отлично, все работает. Также не забудем, что у нас таки есть один отключенный тест. Его можно запустить принудительно, использовав ключ --gtest_also_run_disabled_tests:
runner_vs2008.exe --gtest_also_run_disabled_tests
Получим следующее:
[==========] Running 4 tests from 4 test cases.
[----------] Global test environment set-up.
[----------] 1 test from OpenFileDeathTest
[ RUN ] OpenFileDeathTest.ExitIfNoFile
[ OK ] OpenFileDeathTest.ExitIfNoFile (31 ms)
[----------] 1 test from OpenFileDeathTest (31 ms total)

[----------] 1 test from AssertDeathTest
[ RUN ] AssertDeathTest.DivideByZero
[ OK ] AssertDeathTest.DivideByZero (32 ms)
[----------] 1 test from AssertDeathTest (32 ms total)

[----------] 1 test from AbandonDeathTest
[ RUN ] AbandonDeathTest.ExitCode
[ OK ] AbandonDeathTest.ExitCode (31 ms)
[----------] 1 test from AbandonDeathTest (31 ms total)

[----------] 1 test from BadTest
[ RUN ] BadTest.DISABLED_Test
runner.cpp(72): error: Failed
[ FAILED ] BadTest.DISABLED_Test (0 ms)
[----------] 1 test from BadTest (0 ms total)

[----------] Global test environment tear-down
[==========] 4 tests from 4 test cases ran. (94 ms total)
[ PASSED ] 3 tests.
[ FAILED ] 1 test, listed below:
[ FAILED ] BadTest.DISABLED_Test

1 FAILED TEST
Под занавес отмечу, что появился еще один новый ключ командной строки --help для печати на экран всех весьма многочисленных параметров Google Test.

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

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

  1. Вопрос: есть ли в Google Test Framework возможность подмены вывода штатных сообщений о прохождении тестов?
    Например, мне нужно тестировать код своей библиотеки, вызываемой из основной программы с недоступным исходным кодом.
    Например, разработка внешней компоненты для 1С.

    ОтветитьУдалить
  2. Я немного не понял как связана необходимость подмены штатных сообщений о прохождении тестов и кодом, вызываемым из основной программы с недоступным исходным кодом?

    Если надо изменить вид сообщений типа:

    [ RUN      ] AbandonDeathTest.ExitCode
    [       OK ] AbandonDeathTest.ExitCode (31 ms)

    на

    AbandonDeathTest.ExitCode: Running...
    AbandonDeathTest.ExitCode: OK (31 ms)

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

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

    ОтветитьУдалить
  3. Да, задача состоит именно в том, чтобы запустить тестирование своего кода, вызываемого извне (своя библиотека/плагин, вызываемая из чужого приложения), и не в функции main().

    Каким образом в GTF можно решить эту задачу?

    ОтветитьУдалить
  4. Сходу я универсального решения не могу придумать.

    А на "той" стороне (1С) у вас тоже специальная тестовая среда, которая вызывает для тестов ваш плагин?

    ОтветитьУдалить
  5. "есть ли в Google Test Framework возможность подмены вывода штатных сообщений о прохождении тестов?"

    Не нашел, в отличие кстати, от того же boost.test или CppUnit.

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

    Вот например как это делается для TeamCity http://www.jetbrains.net/confluence/display/TCD4/Build+Script+Interaction+with+TeamCity#BuildScriptInteractionwithTeamCity-ReportingTests

    http://www.jetbrains.net/confluence/display/TW/Cpp+Unit+Test+Reporting

    ОтветитьУдалить
  6. Я может чего-то упускаю, но какая разница, как именно печатаеся вообщение об ошибке? Ведь неудачный unit тест - это причина полной остановки сборки, и суть репорта - это фактически "да" или "нет", собрал или не собрал. Я еще понимаю в QA тестах можно иметь при исправной сборке список сломаных тестов и работать над сокращением данного списка. Вот тут уже нужно иметь удобную систему отчетов, генерируемую по результатам прохода тестов. Но чем это полезно в unit-тестировании?

    ОтветитьУдалить
  7. Разница в том, чтобы сторонняя система автоматизированной сборки смогла разобрать какой именно тест был неудачен, таким образом мы сразу видим источник проблемы, без дополнительных прогонов и расследований (если конечно имя теста вразумительное). Кроме того, это нужно для статистики проекта - когда, сколько тестов, сколько времени и т.д. Очень полезно при анализе тенденций проекта.

    ОтветитьУдалить
  8. В целом, конечно удобно, когда можно настраивать вывод под себя, например, через шаблоны или плагины. И неплохо было бы иметь это в Google Test. Надо будет почитать, может у гугловцев это уже в планах.

    Но с другой стороны можно подкрутить и стороннюю систему автоматизированной сборки для разбора вывода Google Test'а.

    ОтветитьУдалить
  9. Имхо, во-первых парсинга и не надо для юнит тестов.
    А во-вторых, согласан с Александром, gTest настолько прост и исходники есть - сделать любой свой формат вывода - раз плюнуть. И шаблоны и листенеры никакие не нужны :)

    ОтветитьУдалить
  10. Объсните пожалуйста, что такое "так называемые смертельные тесты"?
    Достаточно линка на маны в качестве пояснения. Спасибо.

    ОтветитьУдалить
  11. chester: http://code.google.com/p/googletest/wiki/GoogleTestAdvancedGuide#Death_Tests

    Вкратце: "смертельный" тест позволяет выяснить код завершения фрагмента программы. Удобно для тестирования кодов возврата (например, разбор командной строки и аварийного выхода, если есть ошибки в ней) или срабатывания assert'ов.

    ОтветитьУдалить
  12. А как подцепить GTF в проект NetBeans?

    ОтветитьУдалить
  13. Просто добавляете gtest-all.cc и gtest.h в проект и все.

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