Introduction: Why Testing in C++ Is Not a Luxury, but a Necessity
Development in C++ is always a balance between performance and reliability. Memory management errors, undefined behavior (UB), and complex dependencies between modules make C++ one of the most demanding languages in terms of code quality. Without a systematic approach to testing, a project quickly turns into a "black box," where fixing one bug spawns two new ones.
In this article, we will break down three key levels of testing in C++: unit tests, integration tests, and e2e tests (end-to-end). You will learn which tools to use, how to write effective tests, and how to build a testing pyramid for a C++ project.
1. Unit Testing: Checking Every Brick
Unit tests are the foundation of the testing pyramid. They isolate and verify a single logical unit of code: a function, a class method, or a small structure. In C++, unit tests must execute quickly (<1 second for hundreds of tests) and must not depend on external resources (file system, network, databases).
Tools for C++ Unit Testing
- Google Test (gtest) — the most popular framework from Google. It supports assertion macros, fixtures, and parameterized tests.
- Catch2 — a modern alternative with concise syntax and support for BDD style.
- Boost.Test — part of the Boost library, a mature tool with extensive capabilities.
Example of a Unit Test in Google Test
Suppose we have a function for calculating factorial:
// math_utils.h#pragma onceunsigned long long factorial(unsigned int n);// math_utils.cpp#include "math_utils.h"
unsigned long long factorial(unsigned int n) { if (n == 0) return 1; unsigned long long result = 1; for (unsigned int i = 1; i <= n; ++i) { result *= i; } return result;}We write a unit test:
// test_math_utils.cpp#include <gtest/gtest.h>#include "math_utils.h"
TEST(FactorialTest, HandlesZero) { EXPECT_EQ(factorial(0), 1);}
TEST(FactorialTest, HandlesPositiveNumbers) { EXPECT_EQ(factorial(1), 1); EXPECT_EQ(factorial(5), 120); EXPECT_EQ(factorial(10), 3628800);}
TEST(FactorialTest, HandlesLargeInput) { // 20! fits in unsigned long long EXPECT_EQ(factorial(20), 2432902008176640000ULL);}Best practices for unit testing in C++:
- One test — one check. Use
EXPECT_EQfor soft checks andASSERT_EQfor critical checks. - Isolate dependencies using mock objects (Google Mock).
- Test edge cases: zero values, overflow, empty containers.
- Use
TEST_Ffor repeated initialization (fixtures).
2. Integration Testing: How Modules Work Together
Integration tests verify the interaction between multiple modules or components. In C++, this is especially important because errors often occur at the boundaries: incorrect memory management when passing objects, interface mismatches, or multithreading issues.
Unlike unit tests, integration tests can work with real databases, files, or network sockets, but typically in a test environment (e.g., temporary files or Docker containers).
Example of an Integration Test
Suppose we have a Logger module and a FileManager module. We want to verify that logs are correctly written to a file.
// file_logger.h#pragma once#include <string>
class FileLogger {public: FileLogger(const std::string& filename); vo