Testing in C++: Frameworks and Tools
Quality testing is an essential part of C++ development. Unlike dynamic languages, C++ requires special attention to memory management, object lifetimes, and performance. In this article, we will cover three of the most popular unit testing frameworks: Google Test, Catch2, and doctest, as well as touch on additional tools.
Why is testing needed in C++?
C++ is a language with manual memory management and powerful template magic. Without tests, it is easy to introduce:
- Memory leaks and double free
- Undefined behavior (UB) when working with pointers
- Errors in operator overloading and copy constructors
- Regressions when refactoring legacy code
Testing frameworks automate verification and help quickly find problems.
Google Test (gtest)
The most common framework from Google. It supports all major features: ASSERT_* and EXPECT_* macros, parameterized tests, test fixtures, death tests.
Installation (via CMake FetchContent)
include(FetchContent)FetchContent_Declare( googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG release-1.14.0)FetchContent_MakeAvailable(googletest)Simple test example
#include <gtest/gtest.h>
int factorial(int n) { return (n <= 1) ? 1 : n * factorial(n - 1);}
TEST(FactorialTest, HandlesZeroInput) { EXPECT_EQ(factorial(0), 1);}
TEST(FactorialTest, HandlesPositiveInput) { EXPECT_EQ(factorial(5), 120); EXPECT_EQ(factorial(3), 6);}
int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS();}Fixtures and parameterized tests
class MathFixture : public ::testing::Test {protected: void SetUp() override { /* initialization */ } void TearDown() override { /* cleanup */ }};
// Parameterized testclass SumTest : public ::testing::TestWithParam<std::tuple<int, int, int>> {};TEST_P(SumTest, CheckSum) { auto [a, b, expected] = GetParam(); EXPECT_EQ(a + b, expected);}INSTANTIATE_TEST_SUITE_P(SumParams, SumTest, ::testing::Values( std::make_tuple(1, 1, 2), std::make_tuple(0, 0, 0) ));Pros: huge community, CI/CD integration, powerful macros, death test support.
Cons: relatively slow compilation, large executable file size.
Catch2
A modern framework with an emphasis on test readability. It uses BDD-style (Behaviour-Driven Development) and supports sections.
Installation (header-only)
Catch2 is distributed as a single header file. Simply download catch_amalgamated.hpp and include it.
Example with sections
#define CATCH_CONFIG_MAIN#include <catch2/catch.hpp>
int divide(int a, int b) { if (b == 0) throw std::invalid_argument("division by zero"); return a / b;}
TEST_CASE("Division tests", "[math]") { SECTION("Normal division") { REQUIRE(divide(10, 2) == 5); } SECTION("Division by zero") { REQUIRE_THROWS_AS(divide(5, 0), std::invalid_argument); }}BDD-style (Given-When-Then)
SCENARIO("Vector push_back operation", "[container]") { GIVEN("An empty vector") { std::vector<int> v; WHEN("We push one element") { v.push_back(42); THEN("The size becomes 1") { REQUIRE(v.size() == 1); } AND_THEN("The element is correct") { REQUIRE(v[0] == 42);