Testing in C++: Overview of Frameworks and Tools
Testing is an integral part of professional C++ development. Unlike dynamically typed languages, C++ requires special attention to memory management, RAII, and template metaprogramming. Without quality tests, even a small refactoring can lead to hard-to-find bugs. In this article, we will review the main frameworks for unit testing in C++, their features, and usage examples.
Why Test C++ Code?
Main reasons for implementing testing:
- Refactoring safety — tests lock in correct behavior before changes.
- Documentation — tests show how to use the API.
- Regression detection — automatic verification after each commit.
- Edge case validation — working with nullptr, empty containers, overflow.
Popular Frameworks for C++
1. Google Test (gtest)
The most common framework from Google. Supports automatic test registration, ASSERT_* and EXPECT_* macros, parameterized tests, and mocks via Google Mock.
#include <gtest/gtest.h>
int add(int a, int b) { return a + b; }
TEST(AdditionTest, PositiveNumbers) { EXPECT_EQ(add(2, 3), 5); EXPECT_EQ(add(0, 0), 0);}
TEST(AdditionTest, NegativeNumbers) { EXPECT_EQ(add(-1, -2), -3);}
int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS();}Features:
- Support for death tests to verify assert.
- Mock factory (gmock) for dependency isolation.
- Integration with CMake (find_package(GTest)).
2. Catch2
A modern framework with expressive syntax. Does not require separate test registration — the TEST_CASE macro is sufficient. Supports BDD style (Given-When-Then).
#include <catch2/catch_test_macros.hpp>
int factorial(int n) { return n <= 1 ? 1 : n * factorial(n - 1);}
TEST_CASE("Factorials are computed", "[math]") { REQUIRE(factorial(0) == 1); REQUIRE(factorial(1) == 1); REQUIRE(factorial(5) == 120);}
TEST_CASE("Negative input", "[math][edge]") { REQUIRE_THROWS_AS(factorial(-1), std::domain_error);}Features:
- Test data generation (GENERATE).
- Test sectioning (SECTION).
- Automatic main() selection (if CATCH_CONFIG_MAIN is defined).
3. doctest
A lightweight and fast framework with minimal compilation time. Ideal for projects where build speed is important. Syntax is similar to Catch2.
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN#include <doctest/doctest.h>
int divide(int a, int b) { if (b == 0) throw std::invalid_argument("division by zero"); return a / b;}
TEST_CASE("Division") { CHECK(divide(10, 2) == 5); CHECK_THROWS_AS(divide(5, 0), std::invalid_argument);}Features:
- Very fast compilation (header file ~5 KB).
- Support for CHECK, REQUIRE, WARN macros.
- Built-in XML/JSON reporter.
4. Boost.Test
Part of the Boost libraries. Has rich capabilities but requires more boilerplate code. Supports automatic and manual test registration.
#define BOOST_TEST_MODULE MyTest#include <boost/test/included/unit_test.hpp>
BOOST_AUTO_TEST_SUITE(MathTests)
BOOST_AUTO_TEST_CASE(TestSum) { BOOST_CHECK_EQUAL(2 + 2, 4); BOOST_CHECK_CLOSE(3.14159, 3.14, 0.1); // precision 0.1%}
BOOST_AUTO_TEST_CASE(TestFail) { BOOST_REQUIRE_MESSAGE(false, "This test always fails");}
BOOST_AUTO_TEST_SU