Введение
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 — скорость и компактность
