Testing Frameworks
- Chapter 11: Testing Frameworks
- 1. Fundamentals of CTest
- 2. Project Structure for Testing
- 3. Integrating Catch2
- 4. GoogleTest (GTest/GMock) Integration
- 5. Code Coverage with LCOV
- 6. Dynamic Analysis with Valgrind
- Common Pitfalls & Solutions
- Summary
- Multiple Choice Questions
- Answers and Explanations
- CMake Build Problems
- Key Concepts from Chapter 11 Reinforced:
Chapter 11: Testing Frameworks
1. Fundamentals of CTest
Key Concepts:
- Test Automation: CTest provides a standardized way to define and run tests.
- Test Modes:
ctest --build-and-test
: Builds project and runs tests in one command.ctest -N
: Lists available tests without executing them.
- Test Properties: Configure timeouts, retries, labels, and dependencies.
Code Implementation (Basic CTest Setup):
# Enable testing support
enable_testing()# Add a simple test
add_test(NAME MySimpleTest COMMAND my_test_exe)
set_tests_properties(MySimpleTest PROPERTIESTIMEOUT 30LABELS "UnitTest"
)
2. Project Structure for Testing
Key Concepts:
- Separation of Concerns: Place tests in a dedicated
tests/
directory. - Test Discovery: Auto-detect tests using framework-specific mechanisms.
Code Structure:
project_root/
├── CMakeLists.txt
├── src/ # Main source code
└── tests/├── CMakeLists.txt└── unit/ # Unit tests
CMakeLists.txt (tests/):
add_subdirectory(unit)
3. Integrating Catch2
Key Concepts:
- Header-Only Framework: Easy integration via
FetchContent
. - Test Macros: Use
TEST_CASE
andREQUIRE
for assertions.
Code Implementation:
include(FetchContent)
FetchContent_Declare(Catch2GIT_REPOSITORY https://github.com/catchorg/Catch2.gitGIT_TAG v3.3.2
)
FetchContent_MakeAvailable(Catch2)add_executable(MyCatchTest test_catch.cpp)
target_link_libraries(MyCatchTest PRIVATE Catch2::Catch2WithMain)enable_testing()
add_test(NAME CatchTest COMMAND MyCatchTest)
test_catch.cpp:
#include <catch2/catch_test_macros.hpp>TEST_CASE("Basic arithmetic") {REQUIRE(1 + 1 == 2);
}
4. GoogleTest (GTest/GMock) Integration
Key Concepts:
- Framework Setup: Use
find_package
orFetchContent
. - Test Discovery: Auto-register tests with
gtest_discover_tests
.
Code Implementation:
# Using FetchContent
include(FetchContent)
FetchContent_Declare(googletestGIT_REPOSITORY https://github.com/google/googletest.gitGIT_TAG release-1.12.1
)
FetchContent_MakeAvailable(googletest)add_executable(MyGTestTest test_gtest.cpp)
target_link_libraries(MyGTestTest PRIVATE GTest::gtest_main)enable_testing()
include(GoogleTest)
gtest_discover_tests(MyGTestTest)
test_gtest.cpp:
#include <gtest/gtest.h>TEST(TestSuite, BasicAssertion) {EXPECT_EQ(2, 1 + 1);
}
5. Code Coverage with LCOV
Key Concepts:
- Compiler Flags: Enable coverage instrumentation (
--coverage
). - Report Generation: Use
lcov
andgenhtml
to process coverage data.
Code Implementation:
# Enable coverage flags
target_compile_options(my_lib PRIVATE --coverage)
target_link_libraries(my_lib PRIVATE --coverage)# Add custom target for coverage report
add_custom_target(coverageCOMMAND lcov --capture --directory . --output-file coverage.infoCOMMAND genhtml coverage.info --output-directory coverage_reportDEPENDS my_test_exe
)
6. Dynamic Analysis with Valgrind
Key Concepts:
- Memory Leak Detection: Use Valgrind’s
memcheck
. - CTest Integration: Run tests under Valgrind via
ctest -T memcheck
.
Code Implementation:
# Add Valgrind properties to tests
set_tests_properties(MyTest PROPERTIESLABELS "Valgrind"ENVIRONMENT "VALGRIND=valgrind --leak-check=full --error-exitcode=1"
)# Run via: ctest -T memcheck -L Valgrind
Common Pitfalls & Solutions
-
Test Discovery Failure:
- Cause: Missing
enable_testing()
or incorrect test discovery commands. - Fix: Ensure
enable_testing()
is called and usegtest_discover_tests
for GTest.
- Cause: Missing
-
Linking Errors:
- Cause: Missing
target_link_libraries
for test frameworks. - Fix: Link test executables to framework targets (e.g.,
Catch2::Catch2WithMain
).
- Cause: Missing
-
Coverage Data Not Generated:
- Cause: Incorrect compiler/linker flags (
--coverage
). - Fix: Verify flags are applied to both compile and link stages.
- Cause: Incorrect compiler/linker flags (
Summary
This chapter equips you to:
- Automate tests using CTest.
- Integrate popular testing frameworks (Catch2, GoogleTest).
- Generate code coverage reports.
- Perform dynamic analysis with Valgrind.
Multiple Choice Questions
Question 1: CTest Build-and-Test Mode
Which statements about CTest’s build-and-test mode are correct?
A. It compiles the project and runs tests in a single command.
B. It requires explicit invocation of make
before running tests.
C. It uses the --build-and-test
flag to specify source and build directories.
D. It cannot run tests if the build fails.
Question 2: Unit Test Frameworks in CMake
Which steps are required to integrate Catch2 into a CMake project?
A. Use FetchContent
to download Catch2 from a Git repository.
B. Call enable_testing()
in the top-level CMakeLists.txt
.
C. Add target_link_libraries(test_target Catch2::Catch2WithMain)
.
D. Use ctest_add_tests()
to manually register test cases.
Question 3: GoogleTest and GMock
Which statements about GoogleTest (GTest) and GMock are true?
A. GMock requires linking to the gtest_main
library for test execution.
B. find_package(GTest)
automatically detects system-wide installations.
C. Mock classes in GMock use the MOCK_METHOD
macro to define virtual methods.
D. TEST_F()
is used for tests that share a common fixture.
Question 4: Test Coverage Reports
Which steps are necessary to generate test coverage reports with LCOV?
A. Enable the GCOV
compiler flag using add_compile_options(--coverage)
.
B. Run lcov --capture --directory . --output-file coverage.info
.
C. Use add_test(NAME Coverage COMMAND genhtml coverage.info)
to generate HTML.
D. Link the gcov
library to all test targets.
Question 5: Handling Test Failures
Which CTest options correctly handle test failures?
A. --repeat-until-pass 3
retries failed tests up to 3 times.
B. --stop-on-failure
halts testing after the first failure.
C. --schedule-random
randomizes test order to isolate flaky tests.
D. --force-new-ctest-process
ensures failed tests do not affect subsequent ones.
Question 6: Valgrind Integration
Which statements about Valgrind’s Memcheck are correct?
A. CTEST_MEMCHECK_COMMAND
must be set to the Valgrind executable path.
B. --track-origins=yes
helps identify uninitialized memory errors.
C. Memcheck-Cover requires patching Valgrind to generate coverage reports.
D. --leak-check=full
categorizes leaks by severity (e.g., “definitely lost”).
Question 7: Test Filtering and Repeats
Which CTest command-line options filter or repeat tests?
A. -L <regex>
selects tests with labels matching the regex.
B. -R <regex>
includes tests whose names match the regex.
C. --repeat <n>
runs all tests n
times regardless of previous results.
D. --tests-regex
is deprecated in favor of -R
.
Question 8: LCOV Configuration
Which steps are required to generate HTML coverage reports with LCOV?
A. Use file(COPY ${LCOV_PATH}/scripts/perl DESTINATION ...)
to install Perl scripts.
B. Run genhtml -o coverage_report coverage.info
after capturing coverage data.
C. Exclude system headers using lcov --remove coverage.info '/usr/*' ...
.
D. Add --coverage
to both CMAKE_CXX_FLAGS
and CMAKE_EXE_LINKER_FLAGS
.
Question 9: Unit Test Structure
Which practices improve unit test structure in CMake projects?
A. Placing test code in a tests/
subdirectory with a separate CMakeLists.txt
.
B. Using add_subdirectory(tests)
after defining the main project targets.
C. Defining test fixtures with gtest_add_tests()
for GoogleTest.
D. Avoiding enable_testing()
to prevent conflicts with external test frameworks.
Question 10: Avoiding SEGFAULT Gotchas
Which strategies prevent SEGFAULT errors from disrupting test execution?
A. Running tests in isolated processes using --force-new-ctest-process
.
B. Using --timeout 60
to terminate tests that hang indefinitely.
C. Wrapping test execution in a script that ignores segmentation faults.
D. Enabling ASAN (Address Sanitizer) to detect memory corruption.
Answers and Explanations
Question 1
Correct Answers: A, C
- A: Correct.
ctest --build-and-test
compiles and tests in one step. - C: Correct. The
--build-and-test
flag requires specifying source/build dirs. - B: Incorrect. Build-and-test mode automates building; no manual
make
is needed. - D: Incorrect. Tests are skipped if the build fails, but the command itself runs.
Question 2
Correct Answers: A, B, C
- A: Correct.
FetchContent
is used to fetch Catch2 (Example in §11.3). - B: Correct.
enable_testing()
is required to activate CTest. - C: Correct. Linking to
Catch2::Catch2WithMain
provides the test main. - D: Incorrect. Catch2 auto-registers tests via macros;
ctest_add_tests
is obsolete.
Question 3
Correct Answers: B, C, D
- B: Correct.
find_package(GTest)
locates installed GTest (requiresFindGTest
module). - C: Correct. GMock uses
MOCK_METHOD
to mock virtual methods (§11.3.2). - D: Correct.
TEST_F()
defines fixture-based tests. - A: Incorrect.
gtest_main
is for standalone tests; GMock links togmock
.
Question 4
Correct Answers: A, B
- A: Correct.
--coverage
(or-fprofile-arcs -ftest-coverage
) enables coverage. - B: Correct.
lcov --capture
gathers coverage data. - C: Incorrect.
genhtml
is run manually, not viaadd_test
. - D: Incorrect.
gcov
is a compiler tool, not a library to link.
Question 5
Correct Answers: A, B
- A: Correct.
--repeat-until-pass
retries until success or max attempts. - B: Correct.
--stop-on-failure
stops testing after the first failure. - C: Incorrect.
--schedule-random
shuffles execution order but doesn’t isolate flaky tests. - D: Incorrect.
--force-new-ctest-process
isolates tests but doesn’t handle failures.
Question 6
Correct Answers: A, B, D
- A: Correct.
CTEST_MEMCHECK_COMMAND
must point to Valgrind (§12.2). - B: Correct.
--track-origins=yes
traces uninitialized values. - C: Incorrect. Memcheck-Cover is a Valgrind tool; no patching is needed.
- D: Correct.
--leak-check=full
categorizes leaks (e.g., “definitely lost”).
Question 7
Correct Answers: A, B
- A: Correct.
-L
filters tests by label. - B: Correct.
-R
includes tests matching a name regex. - C: Incorrect.
--repeat
reruns failed tests, not all tests. - D: Incorrect.
--tests-regex
is not a valid CTest option.
Question 8
Correct Answers: B, C, D
- B: Correct.
genhtml
generates HTML fromcoverage.info
. - C: Correct. Excluding system headers avoids noise in reports.
- D: Correct.
--coverage
must be added to both compile and linker flags. - A: Incorrect. LCOV Perl scripts are not manually copied; they come with LCOV.
Question 9
Correct Answers: A, B
- A: Correct. A
tests/
subdirectory isolates test code. - B: Correct.
add_subdirectory(tests)
ensures tests see main targets. - C: Incorrect.
gtest_add_tests
is deprecated; useGoogleTest
module. - D: Incorrect.
enable_testing()
is mandatory for CTest integration.
Question 10
Correct Answers: A, B, D
- A: Correct. Isolated processes prevent SEGFAULTs from crashing the test runner.
- B: Correct. Timeouts terminate hanging tests.
- D: Correct. ASAN detects memory errors early.
- C: Incorrect. Ignoring SEGFAULTs masks issues; it’s not a valid strategy.
CMake Build Problems
Problem 1: Complex Multi-Framework Test Integration
Scenario:
Your project uses both Catch2 and GoogleTest for different test suites. Write a CMake configuration that:
- Compiles
unit_tests_catch.cpp
(using Catch2) andunit_tests_gtest.cpp
(using GoogleTest) as separate executables. - Ensures CTest recognizes all tests from both frameworks.
- Adds a custom test label
SMOKE
to all Catch2 tests. - Runs GoogleTest tests with a 10-second timeout but Catch2 tests with no timeout.
Answer:
# Enable testing
enable_testing()# Add Catch2 tests
add_executable(TestsCatch unit_tests_catch.cpp)
target_link_libraries(TestsCatch PRIVATE Catch2::Catch2WithMain)
add_test(NAME CatchTests COMMAND TestsCatch)
set_tests_properties(CatchTests PROPERTIESLABELS "SMOKE"TIMEOUT 0 # No timeout
)# Add GoogleTest tests
add_executable(TestsGTest unit_tests_gtest.cpp)
target_link_libraries(TestsGTest PRIVATE GTest::GTest GTest::Main)
gtest_discover_tests(TestsGTestTEST_TIMEOUT 10PROPERTIES LABELS "CORE"
)
Explanation:
enable_testing()
is required for CTest integration.- Catch2 tests are explicitly added via
add_test
, while GoogleTest usesgtest_discover_tests
for automatic test discovery. set_tests_properties
customizes Catch2 test behavior (labels and timeout).- GoogleTest’s
TEST_TIMEOUT
ensures a 10-second timeout.
Problem 2: Coverage Report with Conditional Flags
Scenario:
Configure CMake to generate code coverage reports using lcov
only in Debug builds, with the following constraints:
- Coverage flags (
-fprofile-arcs -ftest-coverage
) must not affect non-Debug builds. - Generate an HTML report in
coverage/
after running tests. - Ensure
.gcda
files are cleaned before each test run.
Answer:
# Add coverage flags conditionally
if(CMAKE_BUILD_TYPE STREQUAL "Debug")add_compile_options(--coverage)add_link_options(--coverage)
endif()# Custom target to generate coverage
add_custom_target(coverageCOMMAND lcov --directory . --zerocountersCOMMAND ctestCOMMAND lcov --directory . --capture --output-file coverage.infoCOMMAND genhtml coverage.info --output-directory coverageWORKING_DIRECTORY ${CMAKE_BINARY_DIR}COMMENT "Generating coverage report..."
)
Explanation:
- Flags are added only in Debug builds via
if(CMAKE_BUILD_TYPE...)
. lcov --zerocounters
cleans old.gcda
files before testing.ctest
runs tests to generate fresh coverage data.- The
coverage
target orchestrates the entire workflow, ensuring isolation from non-Debug builds.
Problem 3: Cross-Platform Test Exclusion Using Generator Expressions
Scenario:
Configure tests to:
- Skip all tests tagged
WindowsOnly
on non-Windows platforms. - Skip tests tagged
Slow
when theSKIP_SLOW_TESTS
CMake option isON
. - Ensure skipped tests are reported in CTest output.
Answer:
option(SKIP_SLOW_TESTS "Skip slow tests" OFF)# Add a test with labels
add_executable(PlatformTests platform_tests.cpp)
target_link_libraries(PlatformTests PRIVATE GTest::GTest)
gtest_discover_tests(PlatformTestsPROPERTIESLABELS "WindowsOnly;Slow"
)# Conditionally exclude tests
set_tests_properties(PlatformTests PROPERTIESSKIP_REGULAR_EXPRESSION "$<IF:$<BOOL:${SKIP_SLOW_TESTS}>,Slow,^$>"SKIP_REGULAR_EXPRESSION "$<IF:$<STREQUAL:$<PLATFORM_ID>,Windows>,^$,WindowsOnly>"
)
Explanation:
SKIP_REGULAR_EXPRESSION
uses generator expressions for conditional logic.$<BOOL:${SKIP_SLOW_TESTS}>
evaluates to1
if the option isON
, causing tests labeledSlow
to be skipped.$<STREQUAL:$<PLATFORM_ID>,Windows>
skipsWindowsOnly
tests on non-Windows platforms.
- CTest will report skipped tests with their labels, ensuring visibility.
Key Concepts from Chapter 11 Reinforced:
- CTest Integration: Proper use of
enable_testing()
,add_test
, and framework-specific commands (e.g.,gtest_discover_tests
). - Conditional Configuration: Using generator expressions (
$<IF>
,$<STREQUAL>
) and build-type checks to control test behavior. - Coverage Workflows: Isolating coverage flags to specific build types and orchestrating
lcov
/genhtml
in custom targets. - Test Labels and Filtering: Leveraging labels for test categorization and exclusion logic.