Когда вставал вопрос о тестировании кода, я не задумываясь использовал boost::test. Для расширения кругозора попробовал Google Test Framework. Помимо всяких имеющихся в нем плюшек, в отличии от boost::test проект бурно развивается. Хотел бы поделиться приобретенными знаниями. Всем кому интересно прошу
Ключевым понятием в Google test framework является понятие утверждения (assert). Утверждение представляет собой выражение, результатом выполнения которого может быть успех (success), некритический отказ (nonfatal failure) и критический отказ (fatal failure). Критический отказ вызывает завершение выполнения теста, в остальных случаях тест продолжается. Сам тест представляет собой набор утверждений. Кроме того, тесты могут быть сгруппированы в наборы (test case). Если сложно настраиваемая группа объектов должна быть использована в различных тестах, можно использовать фиксации (fixture). Объединенные наборы тестов являются тестовой программой (test program).
Утверждения, порождающие в случае их ложности критические отказы начинаются с ASSERT_, некритические — EXPECT_. Следует иметь ввиду, что в случае критического отказа выполняется немедленный возврат из функции, в которой встретилось вызвавшее отказ утверждение. Если за этим утверждением идет какой-то очищающий память код или какие-то другие завершающие процедуры, можете получить утечку памяти.
Имеются следующие утверждения (некритические начинаются не с ASSERT_, а с EXPECT_):
Можно написать собственную функцию, возвращающую AssertionResult
Можно контролировать типы данных с помощью функции ::testing::StaticAssertTypeEq<T1, T2>(). Компиляция пройдет с ошибкой в случае несовпадения типов T1 и T2.
В случае ложности утверждения, выдаются данные, использованные в утверждении. Кроме того, можно задать собственный комментарий:
Можно использовать расширенные наборы символов (wchar_t) как в комментариях, так и в утверждениях, касающихся строк. При этом выдача будет в UTF-8 кодировке.
Для определения теста используется макрос TEST. Он определяет void функцию, в которой можно использовать утверждения. Как отмечалось ранее, критический отказ вызывает немедленный возврат из функции.
TEST принимает 2 параметра, уникально идентифицирующие тест, — название тестового набора и название теста. В рамках одного и того же тестового набора названия тестов не должны совпадать. Если название начинается с DISABLED_, это означает, что вы пометили тест (набор тестов) как временно не используемый.
Можно использовать утверждения не только в составе теста, но и вызывать их из любой функции. Имеется лишь одно ограничение — утверждения, порождающие критические отказы не могут быть вызваны из не void функций.
Случается, что объекты, участвующие в тестировании, сложно настраиваются для каждого теста. Можно задать процесс настройки один раз и исполнять его для каждого теста автоматически. В таких ситуациях используются фиксации.
Фиксация представляет собой класс, унаследованный от ::testing::Test, внутри которого объявлены все необходимые для тестирования объекты при этом в конструкторе либо функции SetUp() выполняется их настройка, а в функции TearDown() освобождение ресурсов. Сами тесты, в которых используются фиксации, должны быть объявлены с помощью макроса TEST_F, в качестве первого параметра которого должно быть указано не название набора тестов, а название фиксации.
Для каждого теста будет создана новая фиксация, настроена с помощью SetUp(), запущен тест, освобождены ресурсы с помощью TearDown() и удален объект фиксации. Таким образом каждый тест будет иметь свою копию фиксации «не испорченную» предыдущим тестом.
В некоторых случаях создание тестируемых объектов является очень дорогой операцией, а тесты не вносят никаких изменений в объекты. В таком случае можно не создавать фиксации заново для каждого теста, а использовать распределенную фиксацию с глобальным SetUp()и TearDown(). Фиксация автоматически становится распределенной, если в классе имеется хотя бы один статический член. Статические функции SetUpTestCase() и TearDownTestCase() будут вызываться для настройки объекта и освобождения ресурсов соответственно. Таким образом, набор тестов перед первым тестом вызовет SetUpTestCase(), а после последнего TearDownTestCase().
Если существует потребность в SetUp() и TearDown() для всей программы тестирования, а не только для набора теста, необходимо создать класс-наследник для ::testing::Environment, переопределить SetUp() и TearDown() и зарегистрировать его с помощью функции AddGlobalTestEnvironment.
Объявив все необходимые тесты, мы можем запустить их с помощью функции RUN_ALL_TESTS(). Функцию можно вызывать только один раз. Желательно, чтобы тестовая программа возвращала результат работы функции RUN_ALL_TESTS(), так как некоторые автоматические средства тестирования определяют результат выполнения тестовой программы по тому, что она возвращает.
Вызванная перед RUN_ALL_TESTS() функция InitGoogleTest(argc, argv) делает вашу тестовую программу не просто исполняемым файлом, выводящим на экран результаты тестирования. Это целостное приложение, принимающие на вход параметры, меняющие его поведение. Как обычно ключи -h, --help дадут вам список всех поддерживаемых параметров. Перечислю некоторые из них (за полным списком можно обратиться к документации).
Если вы используете какие-то параметры постоянно, можете задать соответствующую переменную окружения и запускать исполняемый файл без параметров. Например задание переменной GTEST_ALSO_RUN_DISABLED_TESTS ненулевого значения эквивалентно использованию флага --gtest_also_run_disabled_tests.
В данном посте я кратко пробежался по основным функциям Google Test Framework. За более подробными сведениями следует обратиться к документации. Оттуда вы сможете почерпнуть информацию о ASSERT_DEATH используемом при падении программы, о ведении дополнительных журналов, о параметризованных тестах, настройке вывода, тестировании закрытых членов класса и многое другое.
UPD: По справедливому замечанию хабрапользователя nikel добавлена краткая инофрмация по использованию флагов.
UPD 2: Исправление разметки после изменений на Хабре (нативный тег source).
Ключевые понятия
Ключевым понятием в Google test framework является понятие утверждения (assert). Утверждение представляет собой выражение, результатом выполнения которого может быть успех (success), некритический отказ (nonfatal failure) и критический отказ (fatal failure). Критический отказ вызывает завершение выполнения теста, в остальных случаях тест продолжается. Сам тест представляет собой набор утверждений. Кроме того, тесты могут быть сгруппированы в наборы (test case). Если сложно настраиваемая группа объектов должна быть использована в различных тестах, можно использовать фиксации (fixture). Объединенные наборы тестов являются тестовой программой (test program).
Утверждения (assertion)
Утверждения, порождающие в случае их ложности критические отказы начинаются с ASSERT_, некритические — EXPECT_. Следует иметь ввиду, что в случае критического отказа выполняется немедленный возврат из функции, в которой встретилось вызвавшее отказ утверждение. Если за этим утверждением идет какой-то очищающий память код или какие-то другие завершающие процедуры, можете получить утечку памяти.
Имеются следующие утверждения (некритические начинаются не с ASSERT_, а с EXPECT_):
Простейшие логические
- ASSERT_TRUE(condition);
- ASSERT_FALSE(condition);
Сравнение
- ASSERT_EQ(expected, actual); — =
- ASSERT_NE(val1, val2); — !=
- ASSERT_LT(val1, val2); — <
- ASSERT_LE(val1, val2); — <=
- ASSERT_GT(val1, val2); — >
- ASSERT_GE(val1, val2); — >=
Сравнение строк
- ASSERT_STREQ(expected_str, actual_str);
- ASSERT_STRNE(str1, str2);
- ASSERT_STRCASEEQ(expected_str, actual_str); — регистронезависимо
- ASSERT_STRCASENE(str1, str2); — регистронезависимо
Проверка на исключения
- ASSERT_THROW(statement, exception_type);
- ASSERT_ANY_THROW(statement);
- ASSERT_NO_THROW(statement);
Проверка предикатов
- ASSERT_PREDN(pred, val1, val2, ..., valN); — N <= 5
- ASSERT_PRED_FORMATN(pred_format, val1, val2, ..., valN); — работает аналогично предыдущей, но позволяет контролировать вывод
Сравнение чисел с плавающей точкой
- ASSERT_FLOAT_EQ(expected, actual); — неточное сравнение float
- ASSERT_DOUBLE_EQ(expected, actual); — неточное сравнение double
- ASSERT_NEAR(val1, val2, abs_error); — разница между val1 и val2 не превышает погрешность abs_error
Вызов отказа или успеха
- SUCCEED();
- FAIL();
- ADD_FAILURE();
- ADD_FAILURE_AT(«file_path», line_number);
Можно написать собственную функцию, возвращающую AssertionResult
::testing::AssertionResult IsTrue(bool foo)
{
if (foo)
return ::testing::AssertionSuccess();
else
return ::testing::AssertionFailure() << foo << " is not true";
}
TEST(MyFunCase, TestIsTrue)
{
EXPECT_TRUE(IsTrue(false));
}
Можно контролировать типы данных с помощью функции ::testing::StaticAssertTypeEq<T1, T2>(). Компиляция пройдет с ошибкой в случае несовпадения типов T1 и T2.
В случае ложности утверждения, выдаются данные, использованные в утверждении. Кроме того, можно задать собственный комментарий:
ASSERT_EQ(1, 0) << "1 is not equal 0";
Можно использовать расширенные наборы символов (wchar_t) как в комментариях, так и в утверждениях, касающихся строк. При этом выдача будет в UTF-8 кодировке.
Тесты (tests)
Для определения теста используется макрос TEST. Он определяет void функцию, в которой можно использовать утверждения. Как отмечалось ранее, критический отказ вызывает немедленный возврат из функции.
TEST(test_case_name, test_name)
{
ASSERT_EQ(1, 0) << "1 is not equal 0";
}
TEST принимает 2 параметра, уникально идентифицирующие тест, — название тестового набора и название теста. В рамках одного и того же тестового набора названия тестов не должны совпадать. Если название начинается с DISABLED_, это означает, что вы пометили тест (набор тестов) как временно не используемый.
Можно использовать утверждения не только в составе теста, но и вызывать их из любой функции. Имеется лишь одно ограничение — утверждения, порождающие критические отказы не могут быть вызваны из не void функций.
Фиксации (fixtures)
Случается, что объекты, участвующие в тестировании, сложно настраиваются для каждого теста. Можно задать процесс настройки один раз и исполнять его для каждого теста автоматически. В таких ситуациях используются фиксации.
Фиксация представляет собой класс, унаследованный от ::testing::Test, внутри которого объявлены все необходимые для тестирования объекты при этом в конструкторе либо функции SetUp() выполняется их настройка, а в функции TearDown() освобождение ресурсов. Сами тесты, в которых используются фиксации, должны быть объявлены с помощью макроса TEST_F, в качестве первого параметра которого должно быть указано не название набора тестов, а название фиксации.
Для каждого теста будет создана новая фиксация, настроена с помощью SetUp(), запущен тест, освобождены ресурсы с помощью TearDown() и удален объект фиксации. Таким образом каждый тест будет иметь свою копию фиксации «не испорченную» предыдущим тестом.
#include <gtest/gtest.h>
#include <iostream>
class Foo
{
public:
Foo()
: i(0)
{
std::cout << "CONSTRUCTED" << std::endl;
}
~Foo()
{
std::cout << "DESTRUCTED" << std::endl;
}
int i;
};
class TestFoo : public ::testing::Test
{
protected:
void SetUp()
{
foo = new Foo;
foo->i = 5;
}
void TearDown()
{
delete foo;
}
Foo *foo;
};
TEST_F(TestFoo, test1)
{
ASSERT_EQ(foo->i, 5);
foo->i = 10;
}
TEST_F(TestFoo, test2)
{
ASSERT_EQ(foo->i, 5);
}
int main(int argc, char *argv[])
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
В некоторых случаях создание тестируемых объектов является очень дорогой операцией, а тесты не вносят никаких изменений в объекты. В таком случае можно не создавать фиксации заново для каждого теста, а использовать распределенную фиксацию с глобальным SetUp()и TearDown(). Фиксация автоматически становится распределенной, если в классе имеется хотя бы один статический член. Статические функции SetUpTestCase() и TearDownTestCase() будут вызываться для настройки объекта и освобождения ресурсов соответственно. Таким образом, набор тестов перед первым тестом вызовет SetUpTestCase(), а после последнего TearDownTestCase().
Если существует потребность в SetUp() и TearDown() для всей программы тестирования, а не только для набора теста, необходимо создать класс-наследник для ::testing::Environment, переопределить SetUp() и TearDown() и зарегистрировать его с помощью функции AddGlobalTestEnvironment.
Запуск тестов
Объявив все необходимые тесты, мы можем запустить их с помощью функции RUN_ALL_TESTS(). Функцию можно вызывать только один раз. Желательно, чтобы тестовая программа возвращала результат работы функции RUN_ALL_TESTS(), так как некоторые автоматические средства тестирования определяют результат выполнения тестовой программы по тому, что она возвращает.
Флаги
Вызванная перед RUN_ALL_TESTS() функция InitGoogleTest(argc, argv) делает вашу тестовую программу не просто исполняемым файлом, выводящим на экран результаты тестирования. Это целостное приложение, принимающие на вход параметры, меняющие его поведение. Как обычно ключи -h, --help дадут вам список всех поддерживаемых параметров. Перечислю некоторые из них (за полным списком можно обратиться к документации).
- ./test --gtest_filter=TestCaseName.*-TestCaseName.SomeTest — запустить все тесты набора TestCaseName за исключением SomeTest
- ./test --gtest_repeat=1000 --gtest_break_on_failure — запустить тестирующую программу 1000 раз и остановиться при первой неудаче
- ./test --gtest_output=«xml:out.xml» — помимо выдачи в std::out будет создан out.xml — XML отчет с результатами выполнения тестовой программы
- ./test --gtest_shuffle — запускать тесты в случайном порядке
Если вы используете какие-то параметры постоянно, можете задать соответствующую переменную окружения и запускать исполняемый файл без параметров. Например задание переменной GTEST_ALSO_RUN_DISABLED_TESTS ненулевого значения эквивалентно использованию флага --gtest_also_run_disabled_tests.
Вместо заключения
В данном посте я кратко пробежался по основным функциям Google Test Framework. За более подробными сведениями следует обратиться к документации. Оттуда вы сможете почерпнуть информацию о ASSERT_DEATH используемом при падении программы, о ведении дополнительных журналов, о параметризованных тестах, настройке вывода, тестировании закрытых членов класса и многое другое.
UPD: По справедливому замечанию хабрапользователя nikel добавлена краткая инофрмация по использованию флагов.
UPD 2: Исправление разметки после изменений на Хабре (нативный тег source).