Lesson 25 · Quality & debugging
Testing with CTest
Register and run tests with CTest. enable_testing, add_test, discover_tests for GTest, and the ctest command that runs everything.
Your code has tests. CMake’s job is to run them all with one command and tell you which failed. That is CTest, a test driver that runs registered tests and reports results.
The minimum
tests/CMakeLists.txtenable_testing() add_test(NAME test_calc COMMAND test_calc)
ctest --test-dir build
Test project .../build
Start 1: test_calc
1/1 Test #1: test_calc ................. Passed 0.01 sec
100% tests passed, 0 tests failed out of 1
Note
enable_testing(). Call it at the top level of your project, because that is where ctest looks for the test file.add_testthen registers each test executable. A test passes if it exits with code 0.
The add_test signature to use
Use the NAME ... COMMAND ... form. The old one-argument form still exists, but it is less flexible. The named form supports generator expressions, target names and options:
add_test(NAME test_calc
COMMAND test_calc --quick
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/data
)
WORKING_DIRECTORY matters more than you think. Tests that read data files break mysteriously when CTest runs them from the build root instead of the test folder. Set it explicitly.
Enabling and disabling tests per build
top-level CMakeLists.txtinclude(CTest) # defines BUILD_TESTING (default ON) if(BUILD_TESTING) add_subdirectory(tests) endif()
cmake -S . -B build -DBUILD_TESTING=OFF # build without tests
include(CTest) gives you the standard BUILD_TESTING switch, the ecosystem’s agreed knob. It also calls enable_testing() for you.
One test per executable, or discover
If you use GoogleTest, CTest can discover every TEST(...) inside your test binaries automatically, including new ones you add without touching CMake:
tests/CMakeLists.txtinclude(FetchContent) # lesson 18 FetchContent_Declare(googletest GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG 703bd9caab50b139428cea1aaff9974ebee5742e ) FetchContent_MakeAvailable(googletest) add_executable(test_calc test_calc.cpp) target_link_libraries(test_calc PRIVATE calc GTest::gtest_main) # NEW in CMake 4.4. discover_tests() replaces the old # gtest_discover_tests() and include(GoogleTest) dance. discover_tests(test_calc)
Each TEST() in test_calc.cpp becomes its own CTest entry, with failure output on failure. discover_tests is new in CMake 4.4. The old module-based gtest_discover_tests still exists, but the new command is the intended path.
Running tests
ctest --test-dir build # all tests
ctest --test-dir build -R test_calc # filter by name (regex)
ctest --test-dir build --output-on-failure # show failing test output
ctest --test-dir build -j 8 # run in parallel
ctest --test-dir build --preset dev # presets from lesson 20
Pitfall
ctestwithout--test-dirmust run from the build directory, or ctest finds nothing or tests the wrong build. Always pass--test-dir, or use atestPresetsentry soctest --presethandles it.
Tip
Make CI run exactly
ctest --test-dir build --output-on-failure. The--output-on-failureflag is the difference between “CI failed, why?” and a log that tells you which assertion broke.
Read the docs