C++ Unit Testing with GoogleTest
Set up GoogleTest in a CMake project and write clear unit tests with assertions, fixtures, and parameterized tests for confident C++ code.
What you'll learn
- ✓Add GoogleTest to a CMake project with FetchContent
- ✓Write TEST and TEST_F cases with assertions
- ✓Use fixtures to share setup across tests
- ✓Apply parameterized tests for data-driven cases
- ✓Integrate tests into your build with ctest
Prerequisites
- •CMake basics — see /blog/cpp-cmake-tutorial
GoogleTest is the most widely used C++ unit testing framework. It is straightforward to integrate, has rich assertions, and plays well with CMake and CI. This article walks through setting it up and writing your first useful tests.
What and Why
Unit tests give you a safety net for refactoring and a precise specification of behavior. In C++, where mistakes can corrupt memory in distant code, tests are doubly valuable. GoogleTest provides assertion macros, automatic test discovery, fixtures for shared setup, parameterization, and a friendly runner.
The alternative — printf-style scripts or no tests at all — leaves regressions invisible until a user finds them.
Mental Model
A test is a function that exercises code and asserts properties. GoogleTest collects every TEST macro into a global registry. When you run the test binary, it iterates through them, reports pass or fail, and exits with a non-zero code on failure. That non-zero exit code is what CI hooks into.
Fixtures (TEST_F) extend this with a class that runs before and after each test. Parameterized tests (TEST_P) run the same logic across a set of inputs.
Hands-on Example
Add GoogleTest to CMake and write a small test suite.
include(FetchContent)
FetchContent_Declare(googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0)
FetchContent_MakeAvailable(googletest)
enable_testing()
add_executable(tests tests/calc_test.cpp src/calc.cpp)
target_include_directories(tests PRIVATE include)
target_link_libraries(tests PRIVATE GTest::gtest_main)
include(GoogleTest)
gtest_discover_tests(tests)
#include <gtest/gtest.h>
#include "calc.hpp"
TEST(CalcTest, AddsPositives) {
EXPECT_EQ(add(2, 3), 5);
}
TEST(CalcTest, HandlesZero) {
EXPECT_EQ(add(0, 7), 7);
}
class CalcFixture : public ::testing::Test {
protected:
void SetUp() override { /* shared setup */ }
};
TEST_F(CalcFixture, Negatives) {
EXPECT_EQ(add(-2, -3), -5);
}
main(gtest_main)
│
v
discover TEST macros ──> registry
│
v
for each test:
SetUp() ──> body ──> TearDown() ──> record pass/fail
│
v
print summary, exit with status code Run cmake --build build && ctest --test-dir build to see pass/fail. Each failing assertion prints the file, line, and values involved.
Common Pitfalls
A common mistake is using ASSERT_* everywhere when EXPECT_* is enough. ASSERT_* returns from the current function on failure, useful when later lines would crash. EXPECT_* records failure and continues, giving you more information per run.
Another pitfall is non-deterministic tests. Tests that depend on timing, the network, or random seeds give flaky failures. Inject clocks and RNGs through interfaces, and seed them deterministically in tests.
Sharing state across tests via global variables creates order dependencies. GoogleTest may run tests in any order; rely on fixtures, not globals.
Forgetting gtest_discover_tests(tests) means CTest will only see one entry instead of one per test case. The dashboard becomes useless.
Practical Tips
Name tests with the pattern Subject_Action_ExpectedResult to make failures read like English in CI logs.
Use EXPECT_THAT with matchers (from GoogleMock) for richer assertions: EXPECT_THAT(v, ElementsAre(1, 2, 3)) is clearer than three EXPECT_EQ lines.
Keep tests fast. A second-long unit test discourages contributors from running the suite. Reserve slow tests for an integration target.
Use parameterized tests when the same logic must hold for many inputs:
class Squared : public ::testing::TestWithParam<int> {};
TEST_P(Squared, IsNonNegative) {
EXPECT_GE(GetParam() * GetParam(), 0);
}
INSTANTIATE_TEST_SUITE_P(Many, Squared, ::testing::Values(-3, 0, 5));
Hook the test target into CI so every push runs ctest --output-on-failure. A green build is the daily proof that nothing obvious is broken.
Wrap-up
GoogleTest plus CMake plus CTest is the standard modern C++ testing stack. Pull it in with FetchContent, write small TEST cases with informative names, use fixtures to share setup, and lean on parameterization for data-driven coverage. Once tests run on every push, refactoring becomes far less scary and bugs are caught long before users see them.
Related articles
- C++ C++ Design Patterns Overview
Tour the most useful design patterns in modern C++ — singleton, factory, observer, strategy, RAII — and learn when each one earns its keep.
- C++ C++ Exceptions vs Error Codes
Compare exceptions and error codes in C++ for handling failures — performance, ergonomics, and when each style fits real codebases.
- C++ C++ std::string_view Explained
Learn how std::string_view gives you cheap, non-owning views into strings — when to use it, how it speeds up code, and how to avoid dangling references.
- C++ C++ Classes, Constructors, and the Rule of Three
Design C++ classes with constructors, destructors, member initializer lists, and the Rule of Three and Five to manage resources safely.