Введение
Хотя QTest — «родной» фреймворк тестирования Qt, многие проекты используют сторонние фреймворки — Catch2 или Google Test (GTest) — даже для тестирования Qt-кода. В статье сравним три варианта по ключевым критериям: интеграция с Qt-специфичными типами, синтаксис, экосистема matchers/assertion, и дадим практические рекомендации по выбору.
Концепция
QTest глубоко интегрирован с Qt: понимает QString, QList, умеет тестировать сигналы через QSignalSpy, поддерживает data-driven тесты через QTest::addColumn/QTest::newRow, и не требует подключения сторонних зависимостей — что особенно удобно в проектах, уже целиком построенных на Qt инфраструктуре сборки.
Catch2 — современный header-only (в более новых версиях — с возможностью предкомпиляции) фреймворк с выразительным синтаксисом BDD-подобных секций (SECTION), мощными matcher’ами и отличной читаемостью вывода при сбоях. Хорошо подходит, когда тестируется логика, не сильно завязанная на специфику Qt, или для смешанных проектов, где Qt — не единственная технология.
GTest — зрелый, широко распространённый в индустрии фреймворк с богатой экосистемой mock-объектов (GMock), хорошей интеграцией с инструментами CI и анализа покрытия, но с более многословным синтаксисом по сравнению с Catch2.
Пример кода
// --- QTest ---
#include <QtTest/QtTest>
class TestMath : public QObject
{
Q_OBJECT
private slots:
void testAddition_data()
{
QTest::addColumn<int>("a");
QTest::addColumn<int>("b");
QTest::addColumn<int>("expected");
QTest::newRow("positive") << 2 << 3 << 5;
QTest::newRow("negative") << -1 << -1 << -2;
QTest::newRow("zero") << 0 << 5 << 5;
}
void testAddition()
{
QFETCH(int, a);
QFETCH(int, b);
QFETCH(int, expected);
QCOMPARE(a + b, expected);
}
};
QTEST_APPLESS_MAIN(TestMath)
#include "test_math.moc"
// --- Catch2 ---
#include <catch2/catch_test_macros.hpp>
int add(int a, int b) { return a + b; }
TEST_CASE("Сложение целых чисел", "[math]")
{
SECTION("положительные числа") {
REQUIRE(add(2, 3) == 5);
}
SECTION("отрицательные числа") {
REQUIRE(add(-1, -1) == -2);
}
SECTION("с нулём") {
REQUIRE(add(0, 5) == 5);
}
}
// --- GTest ---
#include <gtest/gtest.h>
int add(int a, int b) { return a + b; }
TEST(MathTest, PositiveNumbers) {
EXPECT_EQ(add(2, 3), 5);
}
TEST(MathTest, NegativeNumbers) {
EXPECT_EQ(add(-1, -1), -2);
}
TEST(MathTest, WithZero) {
EXPECT_EQ(add(0, 5), 5);
}
Пояснения к коду
QTest-вариант демонстрирует data-driven тестирование через пару методов testAddition_data()/testAddition() — каждая строка, добавленная через QTest::newRow(), превращается в отдельный отчёт о тесте, а QFETCH извлекает соответствующие значения колонок. Catch2-вариант группирует логически связанные проверки через SECTION внутри одного TEST_CASE, что даёт читаемую структуру и независимое выполнение каждой секции. GTest-вариант наиболее многословен, но это и его сила — явная структура TEST(Suite, Name) хорошо ложится на крупные кодовые базы с сотнями тестов, где важна предсказуемая навигация.
Подводные камни
- Смешивание нескольких тестовых фреймворков в одном проекте без явного разделения усложняет настройку CI и онбординг новых разработчиков — если выбор сделан, стоит придерживаться его консистентно, за исключением обоснованных локальных исключений (например, унаследованный legacy-код на другом фреймворке).
- Catch2/GTest не понимают Qt-специфичные типы «из коробки». Сравнение
QStringчерезREQUIRE/EXPECT_EQобычно работает благодаря перегрузкеoperator<<иoperator==, но для специфичных Qt-конструкций (сигналы, событийный цикл) придётся писать собственные обёртки или продолжать использоватьQSignalSpyповерх стороннего фреймворка — что технически возможно, но усложняет код. - Производительность сборки тестов с GTest/Catch2 в большом проекте. Header-only природа некоторых версий Catch2 может заметно увеличивать время компиляции тестовых файлов — в крупных проектах стоит проверить наличие прекомпилированной библиотечной версии или использовать модульность тестов с разделением на несколько исполняемых файлов.
- Недооценка стоимости миграции существующих QTest-тестов на другой фреймворк. Если проект уже накопил значительное число тестов на QTest, переход на Catch2/GTest «потому что он лучше» редко оправдывает затраты на переписывание — выбор фреймворка для нового проекта и решение о миграции существующего — разные по весу решения.