Тестирование в C++: фреймворки и инструменты
Качественное тестирование — неотъемлемая часть разработки на C++. В отличие от динамических языков, C++ требует особого внимания к управлению памятью, времени жизни объектов и производительности. В этой статье мы разберём три самых популярных фреймворка для модульного тестирования: Google Test, Catch2 и doctest, а также коснёмся дополнительных инструментов.
Зачем нужно тестирование в C++?
C++ — язык с ручным управлением памятью и мощной шаблонной магией. Без тестов легко допустить:
- Утечки памяти и двойное освобождение
- Неопределённое поведение (UB) при работе с указателями
- Ошибки в перегрузке операторов и конструкторах копирования
- Регрессии при рефакторинге легаси-кода
Фреймворки тестирования автоматизируют проверку и помогают быстро находить проблемы.
Google Test (gtest)
Самый распространённый фреймворк от Google. Поддерживает все основные функции: макросы ASSERT_* и EXPECT_*, параметризованные тесты, фикстуры (test fixtures), death-тесты.
Установка (через CMake FetchContent)
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.14.0
)
FetchContent_MakeAvailable(googletest)
Пример простого теста
#include <gtest/gtest.h>
int factorial(int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
TEST(FactorialTest, HandlesZeroInput) {
EXPECT_EQ(factorial(0), 1);
}
TEST(FactorialTest, HandlesPositiveInput) {
EXPECT_EQ(factorial(5), 120);
EXPECT_EQ(factorial(3), 6);
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Фикстуры и параметризованные тесты
class MathFixture : public ::testing::Test {
protected:
void SetUp() override { /* инициализация */ }
void TearDown() override { /* очистка */ }
};
// Параметризованный тест
class SumTest : public ::testing::TestWithParam<std::tuple<int, int, int>> {};
TEST_P(SumTest, CheckSum) {
auto [a, b, expected] = GetParam();
EXPECT_EQ(a + b, expected);
}
INSTANTIATE_TEST_SUITE_P(SumParams, SumTest,
::testing::Values(
std::make_tuple(1, 1, 2),
std::make_tuple(0, 0, 0)
));
Плюсы: огромное сообщество, интеграция с CI/CD, мощные макросы, поддержка death-тестов.
Минусы: относительно медленная компиляция, большой размер исполняемого файла.
Catch2
Современный фреймворк с акцентом на читаемость тестов. Использует BDD-стиль (Behaviour-Driven Development) и поддерживает секции (sections).
Установка (header-only)
Catch2 распространяется как один заголовочный файл. Достаточно скачать catch_amalgamated.hpp и подключить его.
Пример с секциями
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
int divide(int a, int b) {
if (b == 0) throw std::invalid_argument("division by zero");
return a / b;
}
TEST_CASE("Division tests", "[math]") {
SECTION("Normal division") {
REQUIRE(divide(10, 2) == 5);
}
SECTION("Division by zero") {
REQUIRE_THROWS_AS(divide(5, 0), std::invalid_argument);
}
}
BDD-стиль (Given-When-Then)
SCENARIO("Vector push_back operation", "[container]") {
GIVEN("An empty vector") {
std::vector<int> v;
WHEN("We push one element") {
v.push_back(42);
THEN("The size becomes 1") {
REQUIRE(v.size() == 1);
}
AND_THEN("The element is correct") {
REQUIRE(v[0] == 42);
}
}
}
}
Плюсы: очень читаемые тесты, header-only, быстрая компиляция, встроенный генератор тестовых данных.
Минусы: меньше макросов для сложных сценариев, слабее интеграция с death-тестами.
doctest
Лёгкий и