Тестирование в языке C++: подходы, библиотеки

Автор: | 20 июня, 2025

Введение

C++ — язык системного программирования, который широко используется в высокопроизводительных приложениях, включая игры, финансовые системы, операционные системы и встроенное ПО. В таких системах ошибки могут дорого обойтись, поэтому тестирование — неотъемлемая часть разработки.

В этой статье мы рассмотрим:

  • Зачем нужно тестирование в C++
  • Какие виды тестов применяются
  • Популярные библиотеки для тестирования
  • Примеры с Google Test, Catch2 и QTest
  • Подходы к написанию хороших тестов
  • Интеграция с CI/CD

Зачем нужно тестирование?

C++ не имеет встроенного фреймворка для тестирования (как, например, Python или Java), но при этом предоставляет широкие возможности благодаря множеству сторонних библиотек.

Цели тестирования:

  • Проверка логики программы
  • Защита от регрессий
  • Облегчение рефакторинга
  • Документирование поведения функций
  • Интеграция с CI/CD

Типы тестирования в C++

Тип тестаОписание
Unit-тестыПроверка отдельных функций/методов. Самые быстрые и точные.
ИнтеграционныеПроверка взаимодействия между модулями.
СистемныеПроверка всей системы в целом.
РегрессионныеПовторное выполнение тестов, чтобы убедиться, что старый функционал не сломался.
PerformanceПроверка скорости и эффективности.

Популярные фреймворки для тестирования в C++

НазваниеОсобенности
Google Test (GTest)Самый популярный фреймворк от Google. Поддерживает моки, фреймворки, параметризованные тесты.
Catch2Легковесный, заголовочный. Очень простой в использовании.
DoctestСамый быстрый компилятор тестов. Подходит для TDD.
QTest (Qt)Для Qt-приложений. Поддержка GUI и сигналов.
Boost.TestМощный, но сложный. Поддержка всего, но требует времени на освоение.

Пример 1: Google Test

#include <gtest/gtest.h>

int add(int a, int b) {
    return a + b;
}

TEST(MathTest, Addition) {
    EXPECT_EQ(add(2, 3), 5);
    EXPECT_NE(add(2, 2), 5);
}

Пример 2: Catch2

#define CATCH_CONFIG_MAIN
#include "catch.hpp"

int factorial(int n) {
    return (n <= 1) ? 1 : n * factorial(n - 1);
}

TEST_CASE("Factorials are computed", "[factorial]") {
    REQUIRE(factorial(1) == 1);
    REQUIRE(factorial(2) == 2);
    REQUIRE(factorial(3) == 6);
    REQUIRE(factorial(10) == 3628800);
}

Пример 3: QTest (для Qt-проектов)

#include <QtTest>

class TestLogic : public QObject {
    Q_OBJECT

private slots:
    void testMultiply() {
        QCOMPARE(2 * 3, 6);
    }
};

QTEST_MAIN(TestLogic)
#include "testlogic.moc"

Параметризованные тесты

GTest:

class ParamTest : public ::testing::TestWithParam<int> {};
TEST_P(ParamTest, IsEven) {
    int n = GetParam();
    EXPECT_EQ(n % 2, 0);
}
INSTANTIATE_TEST_SUITE_P(EvenNumbers, ParamTest, ::testing::Values(2, 4, 6, 8));

Catch2:

TEST_CASE("Check even", "[even]") {
    for (int n : {2, 4, 6}) {
        REQUIRE(n % 2 == 0);
    }
}

Моки

В C++ моки (от англ. mock objects) — это специальные объекты, используемые в модульном тестировании для имитации поведения реальных компонентов системы. Они позволяют изолировать тестируемый код от внешних зависимостей, таких как базы данных, сетевые сервисы или аппаратные устройства, обеспечивая более контролируемую и предсказуемую среду для тестирования.

Зачем нужны моки?

Моки применяются в следующих случаях:

  • Изоляция тестируемого кода: позволяют тестировать компоненты независимо от их внешних зависимостей.
  • Контроль над поведением зависимостей: можно задать ожидаемые вызовы методов, передаваемые параметры и возвращаемые значения.
  • Тестирование в условиях, трудно воспроизводимых в реальной среде: например, имитация ошибок сети или отказов оборудования.
  • Проверка взаимодействия между компонентами: моки позволяют убедиться, что определённые методы вызываются с нужными параметрами и в нужной последовательности.

Отличие моков от стабов

Важно различать моки и стабы:

  • Стаб (stub): предоставляет предопределённые ответы на вызовы, но не проверяет, были ли эти вызовы совершены.
  • Мок (mock): не только имитирует поведение, но и проверяет, что определённые методы были вызваны с ожидаемыми параметрами и в нужном порядке.

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

GoogleTest + GoogleMock позволяет создавать моки:

#include <gmock/gmock.h>
class Printer {
public:
    virtual void print(const std::string&) = 0;
};
class MockPrinter : public Printer {
public:
    MOCK_METHOD(void, print, (const std::string&), (override));
};
MockPrinter printer;
EXPECT_CALL(printer, print("Hello")).Times(1);

Рекомендации и лучшие практики

✅ Пишите тесты для каждого публичного метода.
✅ Один тест — один кейс.
✅ Используйте осмысленные имена (TEST(Math, SumOfPositives)).
✅ Пишите тест перед кодом (TDD).
✅ Не забывайте про негативные тесты (ошибочные входы).
✅ Используйте моки для внешних зависимостей (база, сеть).
✅ Используйте сборочные флаги -DDEBUG и -DTESTING.


Вывод

Тестирование в C++ — важная часть профессиональной разработки. Несмотря на отсутствие встроенных средств, существует множество мощных и зрелых фреймворков, таких как:

  • Google Test — стандарт индустрии
  • Catch2 — лёгкость и удобство
  • QTest — GUI и Qt-проекты
  • Doctest — скорость и компактность