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_CASEandREQUIREfor 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_packageorFetchContent. - 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
lcovandgenhtmlto 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_testsfor GTest.
- Cause: Missing
-
Linking Errors:
- Cause: Missing
target_link_librariesfor 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-testcompiles and tests in one step. - C: Correct. The
--build-and-testflag requires specifying source/build dirs. - B: Incorrect. Build-and-test mode automates building; no manual
makeis needed. - D: Incorrect. Tests are skipped if the build fails, but the command itself runs.
Question 2
Correct Answers: A, B, C
- A: Correct.
FetchContentis used to fetch Catch2 (Example in §11.3). - B: Correct.
enable_testing()is required to activate CTest. - C: Correct. Linking to
Catch2::Catch2WithMainprovides the test main. - D: Incorrect. Catch2 auto-registers tests via macros;
ctest_add_testsis obsolete.
Question 3
Correct Answers: B, C, D
- B: Correct.
find_package(GTest)locates installed GTest (requiresFindGTestmodule). - C: Correct. GMock uses
MOCK_METHODto mock virtual methods (§11.3.2). - D: Correct.
TEST_F()defines fixture-based tests. - A: Incorrect.
gtest_mainis 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 --capturegathers coverage data. - C: Incorrect.
genhtmlis run manually, not viaadd_test. - D: Incorrect.
gcovis a compiler tool, not a library to link.
Question 5
Correct Answers: A, B
- A: Correct.
--repeat-until-passretries until success or max attempts. - B: Correct.
--stop-on-failurestops testing after the first failure. - C: Incorrect.
--schedule-randomshuffles execution order but doesn’t isolate flaky tests. - D: Incorrect.
--force-new-ctest-processisolates tests but doesn’t handle failures.
Question 6
Correct Answers: A, B, D
- A: Correct.
CTEST_MEMCHECK_COMMANDmust point to Valgrind (§12.2). - B: Correct.
--track-origins=yestraces uninitialized values. - C: Incorrect. Memcheck-Cover is a Valgrind tool; no patching is needed.
- D: Correct.
--leak-check=fullcategorizes leaks (e.g., “definitely lost”).
Question 7
Correct Answers: A, B
- A: Correct.
-Lfilters tests by label. - B: Correct.
-Rincludes tests matching a name regex. - C: Incorrect.
--repeatreruns failed tests, not all tests. - D: Incorrect.
--tests-regexis not a valid CTest option.
Question 8
Correct Answers: B, C, D
- B: Correct.
genhtmlgenerates HTML fromcoverage.info. - C: Correct. Excluding system headers avoids noise in reports.
- D: Correct.
--coveragemust 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_testsis deprecated; useGoogleTestmodule. - 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
SMOKEto 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_testsfor automatic test discovery. set_tests_propertiescustomizes Catch2 test behavior (labels and timeout).- GoogleTest’s
TEST_TIMEOUTensures 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
.gcdafiles 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 --zerocounterscleans old.gcdafiles before testing.ctestruns tests to generate fresh coverage data.- The
coveragetarget 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
WindowsOnlyon non-Windows platforms. - Skip tests tagged
Slowwhen theSKIP_SLOW_TESTSCMake 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_EXPRESSIONuses generator expressions for conditional logic.$<BOOL:${SKIP_SLOW_TESTS}>evaluates to1if the option isON, causing tests labeledSlowto be skipped.$<STREQUAL:$<PLATFORM_ID>,Windows>skipsWindowsOnlytests 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/genhtmlin custom targets. - Test Labels and Filtering: Leveraging labels for test categorization and exclusion logic.
