Введение
Выбрать библиотеку для модульных тестов в C++ непросто: популярных вариантов много, и каждый хвалят по-своему. Лучший способ сравнить их — написать одни и те же тесты на каждом. В этой статье мы протестируем одну простую функцию пятью способами. Фреймворки тестирования C++ в нашем обзоре: Google Test, Catch2, doctest, Boost.Test и QTest. Посмотрим, как выглядит код, как подключить каждую библиотеку в CMake, и разберём, какую из них выбрать для своего проекта.
Тестируемая функция
Возьмём вычисление факториала. Для отрицательных чисел функция возвращает -1 как признак ошибки:
// factorial.h
inline int factorial(int n)
{
if (n < 0) return -1; // ошибка
return (n <= 1) ? 1 : n * factorial(n - 1);
}
В каждом фреймворке проверим положительные значения (0, 5, 10) и обработку отрицательного ввода (-5). Обратите внимание: уже при n = 13 результат не помещается в 32-битный int. Хороший тест должен находить и такие граничные случаи, поэтому в реальном проекте стоит использовать std::uint64_t и проверять переполнение.
Google Test
Самый распространённый фреймворк в индустрии. Богатый набор проверок, параметризованные тесты, встроенная поддержка моков через Google Mock.
// test_gtest.cpp
#include <gtest/gtest.h>
#include "factorial.h"
TEST(FactorialTest, PositiveNumbers) {
EXPECT_EQ(factorial(0), 1);
EXPECT_EQ(factorial(5), 120);
EXPECT_EQ(factorial(10), 3628800);
}
TEST(FactorialTest, NegativeNumber) {
EXPECT_EQ(factorial(-5), -1);
}
find_package(GTest REQUIRED)
add_executable(test_gtest test_gtest.cpp)
target_link_libraries(test_gtest PRIVATE GTest::gtest_main)
Функция main() берётся из библиотеки gtest_main. EXPECT_* продолжает тест после ошибки, а ASSERT_* сразу его прерывает.
Catch2
Лаконичный синтаксис: проверки пишутся обычными выражениями C++, а при ошибке Catch2 показывает значения обеих частей.
// test_catch2.cpp
#include <catch2/catch_test_macros.hpp>
#include "factorial.h"
TEST_CASE("Factorial calculation") {
REQUIRE(factorial(0) == 1);
REQUIRE(factorial(5) == 120);
REQUIRE(factorial(10) == 3628800);
}
TEST_CASE("Factorial with negative input") {
REQUIRE(factorial(-5) == -1);
}
find_package(Catch2 3 REQUIRED)
add_executable(test_catch2 test_catch2.cpp)
target_link_libraries(test_catch2 PRIVATE Catch2::Catch2WithMain)
В старых примерах можно встретить #define CATCH_CONFIG_MAIN и #include "catch.hpp". Это синтаксис Catch2 версии 2, где библиотека была одним заголовком. В Catch2 v3 библиотека компилируется отдельно, а main предоставляет цель Catch2WithMain.
doctest
Синтаксис почти как у Catch2, но библиотека намного легче и компилируется быстрее. Это один заголовочный файл, поэтому её удобно просто положить в проект.
// test_doctest.cpp
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "doctest.h"
#include "factorial.h"
TEST_CASE("Factorial correctness") {
CHECK(factorial(0) == 1);
CHECK(factorial(5) == 120);
CHECK(factorial(10) == 3628800);
CHECK(factorial(-5) == -1);
}
Особенность doctest — тесты можно писать прямо рядом с кодом в рабочих исходниках, а в релизной сборке отключать их макросом DOCTEST_CONFIG_DISABLE.
Boost.Test
Часть библиотек Boost. Мощный, но тяжеловесный вариант. Логичен, если проект уже использует Boost.
// test_boost.cpp
#define BOOST_TEST_MODULE FactorialTest
#include <boost/test/included/unit_test.hpp>
#include "factorial.h"
BOOST_AUTO_TEST_CASE(FactorialPositive) {
BOOST_CHECK_EQUAL(factorial(0), 1);
BOOST_CHECK_EQUAL(factorial(5), 120);
BOOST_CHECK_EQUAL(factorial(10), 3628800);
}
BOOST_AUTO_TEST_CASE(FactorialNegative) {
BOOST_CHECK_EQUAL(factorial(-5), -1);
}
Вариант included/unit_test.hpp подключает всю библиотеку в одну единицу трансляции. Это удобно для старта, но замедляет компиляцию. В больших проектах лучше линковать скомпилированную библиотеку Boost.Test.
QTest
Встроенный фреймворк Qt. Не требует внешних зависимостей и умеет тестировать то, что важно для Qt-приложений: сигналы, события, виджеты, асинхронный код.
// test_qtest.cpp
#include <QtTest>
#include "factorial.h"
class TestFactorial : public QObject
{
Q_OBJECT
private slots:
void testPositive() {
QCOMPARE(factorial(0), 1);
QCOMPARE(factorial(5), 120);
QCOMPARE(factorial(10), 3628800);
}
void testNegative() {
QCOMPARE(factorial(-5), -1);
}
};
QTEST_MAIN(TestFactorial)
#include "test_qtest.moc"
find_package(Qt6 REQUIRED COMPONENTS Test)
set(CMAKE_AUTOMOC ON)
add_executable(test_qtest test_qtest.cpp)
target_link_libraries(test_qtest PRIVATE Qt6::Test)
Строка #include "test_qtest.moc" обязательна, когда класс с Q_OBJECT объявлен прямо в .cpp-файле. Без неё сборка упадёт с ошибками компоновки.
Фреймворки тестирования C++: сравнительная таблица
| Фреймворк | Подключение | Скорость сборки | Сильные стороны |
|---|---|---|---|
| Google Test | Библиотека | Средняя | Стандарт индустрии, моки, параметризация |
| Catch2 v3 | Библиотека | Средняя | Выразительный синтаксис, BDD-стиль, генераторы |
| doctest | Один заголовок | Высокая | Лёгкость, тесты рядом с кодом |
| Boost.Test | Заголовки или библиотека | Низкая | Гибкость, если Boost уже в проекте |
| QTest | Модуль Qt | Высокая | Сигналы, GUI, события, без внешних зависимостей |
Что выбрать
- Проект на Qt — QTest: он уже есть в комплекте и понимает специфику Qt. Для сложной логики его можно сочетать с Google Test.
- Новый проект на чистом C++ — Google Test или Catch2.
- Важна скорость сборки или нужны тесты прямо в исходниках — doctest.
- Проект уже использует Boost — Boost.Test.
Все пять фреймворков интегрируются с CTest через add_test() и поддерживаются Qt Creator, так что тесты можно запускать прямо из среды.
Заключение
Как видно по примерам, базовые проверки во всех фреймворках тестирования C++ выглядят похоже. Разница проявляется в деталях: способе подключения, скорости сборки, удобстве сообщений об ошибках и дополнительных возможностях. Начните с того фреймворка, который проще встроить в ваш проект, и пишите тесты регулярно — это важнее выбора конкретной библиотеки.