Найти в Дзене

Знакомство с библиотекой Google Test

Google Test(gtest) является одной из популярных библиотек модульного тестирования. Тесты не требуется отдельно регистрировать для запуска. Каждый объявленный в программе тест автоматически будет запущен. Тесты можно объединять в группы (наборы). Кроме того, gtest позволяет использовать тестовые классы/фикстуры (test fixture). С их помощью можно создавать и повторно использовать конфигурацию объектов для нескольких различных тестов. Пусть нужно протестировать отдельную функцию с сигнатурой int Fibonacci(int n), осуществляющую расчет n-го числа Фибоначчи. Тест для этой функции может выглядеть так: TEST(FibonacciTest, HandlesZeroInput) {
EXPECT_EQ(0, Fibonacci(0));
} Макрос TEST — это основной инструмент для определения отдельного тестового случая (кейса). Он позволяет программисту создавать автономные тесты, которые gtest автоматически обнаружит и запустит. Общий вид макроса: TEST(TestSuiteName, TestName) { // Здесь размещается тело теста с проверками (assertions) } TestSuiteNam

Google Test(gtest) является одной из популярных библиотек модульного тестирования. Тесты не требуется отдельно регистрировать для запуска. Каждый объявленный в программе тест автоматически будет запущен. Тесты можно объединять в группы (наборы). Кроме того, gtest позволяет использовать тестовые классы/фикстуры (test fixture). С их помощью можно создавать и повторно использовать конфигурацию объектов для нескольких различных тестов.

Пусть нужно протестировать отдельную функцию с сигнатурой int Fibonacci(int n), осуществляющую расчет n-го числа Фибоначчи. Тест для этой функции может выглядеть так:

TEST(FibonacciTest, HandlesZeroInput) {
EXPECT_EQ(0, Fibonacci(0));
}

Макрос TEST — это основной инструмент для определения отдельного тестового случая (кейса). Он позволяет программисту создавать автономные тесты, которые gtest автоматически обнаружит и запустит.

Общий вид макроса:

TEST(TestSuiteName, TestName) {

// Здесь размещается тело теста с проверками (assertions)

}

TestSuiteName (имя набора) задает логическую группировку связанных тестов. В выводе результатов тесты с одинаковым TestSuiteName будут идти вместе.

TestName (имя теста) представляет уникальное название конкретной проверки внутри набора.

Проверки, например, EXPECT_EQ или ASSERT_TRUE называются утверждениями, представляющими собой макросы, аналогичные вызовам функций. Они используются для проверки поведения вашего кода различными способами. Утверждения могут проверять логические условия, сравнивать значения, проверять строковые и числа с плавающей запятой. Если все утверждения, заданные в TEST, выполняются, то тест считается пройденным.

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

В gtest существует два типа утверждений:

  • EXPECT_*
  • ASSERT_*

Утверждение EXPECT_* является некритическим. Если оно не выполняется, проверка продолжается. Кроме того, вы также можете добавлять пользовательские сообщения об ошибках к утверждениям с помощью оператора <<.

Некоторые из часто используемых утверждений EXPECT_*:

  1. EXPECT_TRUE(condition) — для проверки истинности условия.
  2. EXPECT_EQ(val1, val2) — для проверки равенства.
  3. EXPECT_LT(val1, val2) — проверяет, меньше ли val1, чем val2.
  4. EXPECT_GT(val1, val2) — проверяет, больше ли val1, чем val2.
  5. EXPECT_THROW(statement, exception_type) — ожидает выбрасывания определенного исключения.
  6. EXPECT_STREQ(str1, str2) — проверяет, равны ли две заданные строки.

В отличие от EXPECT_*, утверждения ASSERT_* являются фатальными. В случае ошибки текущая функция теста немедленно прерывается, что также предотвращает выполнение любого дальнейшего кода в этом наборе тестов.

Следует использовать ASSERT_*, если продолжение теста не имеет смысла или может привести к вводящим в заблуждение результатам.

Некоторые из часто используемых утверждений ASSERT_*:

  1. ASSERT_TRUE(condition) — проверяет, истинно ли условие.
  2. ASSERT_EQ(val1, val2) — проверяет равенство двух значений.
  3. ASSERT_LT(val1, val2) — проверяет, меньше ли первое значение второго.
  4. ASSERT_GT(val1, val2) — проверяет, больше ли первое значение второго.
  5. ASSERT_THROW(statement, exception_type) — ожидает, что указанный оператор вызовет конкретное исключение.

Кроме TEST также существует макрос TEST_F, (где буква F означает fixture). Он используется, когда вам нужно запустить несколько тестов, работающих с общими данными или с одинаковой настройкой окружения.

В отличие от обычного TEST, который автономен, TEST_F позволяет избежать дублирования кода(принцип DRY), вынося подготовку объектов в отдельный класс.

Для использования TEST_F необходимо выполнить следующие шаги:

  • Создание класса-фикстуры. Вы создаете класс, наследующий от ::testing::Test.
  • Подготовка (SetUp). В методе SetUp можно инициализировать объекты, открывать файлы или подключаться к базе данных.
  • Очистка (TearDown). В методе TearDown() освобождаете ресурсы.
  • Использование в тестах. В макросе TEST_F первым аргументом указывается имя этого класса, а вторым - имя теста(TestName).

В качестве примера рассмотрим тестирование двух стеков:

-2

Ключевыми особенностями подхода c использованием класса-фикстуры являются:

  • Изоляция. Для каждого отдельного макроса TEST_F gtest создает новый экземпляр класса-фикстуры. Изменения данных в одном тесте никак не повлияют на другой.
  • Доступ к полям. Все переменные, объявленные в секции protected или public класса фикстуры, доступны внутри тела теста.

Также gtest позволяет создавать тесты, которые запускаются многократно с разными входными данными, но используют один и тот же программный код (логику проверки). Такие тесты называются параметризованными.

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

Перед началом тестирования нужно создать структуру данных, хранящую входные данные и ожидаемый результат, например:

struct MyParams { int input; int expected; };

Далее создаем класс-фикстуру, наследующий от ::testing::TestWithParam<T>, где T — тип ваших данных (MyParams):

class MathTest : public ::testing::TestWithParam<MyParams> {};

Далее пишем тест, где вместо TEST_F используется макрос TEST_P (буква P означает Parametric). Внутри теста вы получаете данные через метод GetParam(), например:

TEST_P(MathTest, SquareCheck) {

auto params = GetParam();

EXPECT_EQ(params.input * params.input, params.expected);

}

После этого необходимо задать набор данных и передать его в тест при помощи макроса INSTANTIATE_TEST_SUITE_P:

INSTANTIATE_TEST_SUITE_P(MyGroup, MathTest, ::testing::Values(

MyParams{2, 4},

MyParams{3, 9},

MyParams{5, 25}

));

В результате gtest автоматически создаст и запустит 3 отдельных теста, подставляя каждое значение из набора.

Макрос INSTANTIATE_TEST_SUITE_P связывает параметризованный тест, написанный через TEST_P, с конкретным набором данных. У него три основных обязательных параметра и один необязательный для именования:

INSTANTIATE_TEST_SUITE_P(
InstantiationName,
TestSuiteName,
ParamGenerator,
NameGenerator // (опционально)
);

InstantiationName (имя экземпляра) задает уникальное имя для данной группы тестов.

TestSuiteName (имя тестового набора). Должно в точности совпадать с именем класса-фикстуры, который наследует от ::testing::TestWithParam<T>.

ParamGenerator (Генератор параметров):
Самая важная часть, определяющая, какие данные будут переданы в тест. gtest предоставляет встроенные вспомогательные функции (хелперы):

  • Values(v1, v2, ...) — перечисление конкретных значений.
  • ValuesIn(container) — берет данные из массива или вектора.
  • Range(begin, end, step) — генерирует последовательность чисел.
  • Bool() — генерирует true и false.
  • Combine(g1, g2, ...) — создает декартово произведение (все возможные комбинации нескольких наборов).

NameGenerator (необязательный параметр). Функция или лямбда, которая превращает текущий параметр в строку. По умолчанию gtest просто нумерует запуски (0, 1, 2), но с помощью этого параметра в отчете можно увидеть более содержательные названия.

Использование параметризованных тестов позволяет достигать следующих целей:

  • Чистота кода. Не нужно писать множество похожих тестов для разных входных данных.
  • Информативность. Если тест упадет на одном конкретном значении (например, на числе 0), gtest покажет, какой именно набор параметров вызвал ошибку, при этом остальные тесты продолжат выполняться.
  • Гибкость. Можно генерировать параметры динамически (через диапазоны Range, списки ValuesIn или комбинации Combine).
  • Изоляция. Несмотря на то что код один, каждый запуск с новым параметром считается отдельным тестом.

На этом закончим описание типов тестов. В следующей статье рассмотрим тестирование вызовов.