$npx -y skills add affaan-m/ECC --skill cpp-testingUse only when writing/updating/fixing C++ tests, configuring GoogleTest/CTest, diagnosing failing or flaky tests, or adding coverage/sanitizers.
| 1 | # C++ Testing (Agent Skill) |
| 2 | |
| 3 | Agent-focused testing workflow for modern C++ (C++17/20) using GoogleTest/GoogleMock with CMake/CTest. |
| 4 | |
| 5 | ## When to Use |
| 6 | |
| 7 | - Writing new C++ tests or fixing existing tests |
| 8 | - Designing unit/integration test coverage for C++ components |
| 9 | - Adding test coverage, CI gating, or regression protection |
| 10 | - Configuring CMake/CTest workflows for consistent execution |
| 11 | - Investigating test failures or flaky behavior |
| 12 | - Enabling sanitizers for memory/race diagnostics |
| 13 | |
| 14 | ### When NOT to Use |
| 15 | |
| 16 | - Implementing new product features without test changes |
| 17 | - Large-scale refactors unrelated to test coverage or failures |
| 18 | - Performance tuning without test regressions to validate |
| 19 | - Non-C++ projects or non-test tasks |
| 20 | |
| 21 | ## Core Concepts |
| 22 | |
| 23 | - **TDD loop**: red → green → refactor (tests first, minimal fix, then cleanups). |
| 24 | - **Isolation**: prefer dependency injection and fakes over global state. |
| 25 | - **Test layout**: `tests/unit`, `tests/integration`, `tests/testdata`. |
| 26 | - **Mocks vs fakes**: mock for interactions, fake for stateful behavior. |
| 27 | - **CTest discovery**: use `gtest_discover_tests()` for stable test discovery. |
| 28 | - **CI signal**: run subset first, then full suite with `--output-on-failure`. |
| 29 | |
| 30 | ## TDD Workflow |
| 31 | |
| 32 | Follow the RED → GREEN → REFACTOR loop: |
| 33 | |
| 34 | 1. **RED**: write a failing test that captures the new behavior |
| 35 | 2. **GREEN**: implement the smallest change to pass |
| 36 | 3. **REFACTOR**: clean up while tests stay green |
| 37 | |
| 38 | ```cpp |
| 39 | // tests/add_test.cpp |
| 40 | #include <gtest/gtest.h> |
| 41 | |
| 42 | int Add(int a, int b); // Provided by production code. |
| 43 | |
| 44 | TEST(AddTest, AddsTwoNumbers) { // RED |
| 45 | EXPECT_EQ(Add(2, 3), 5); |
| 46 | } |
| 47 | |
| 48 | // src/add.cpp |
| 49 | int Add(int a, int b) { // GREEN |
| 50 | return a + b; |
| 51 | } |
| 52 | |
| 53 | // REFACTOR: simplify/rename once tests pass |
| 54 | ``` |
| 55 | |
| 56 | ## Code Examples |
| 57 | |
| 58 | ### Basic Unit Test (gtest) |
| 59 | |
| 60 | ```cpp |
| 61 | // tests/calculator_test.cpp |
| 62 | #include <gtest/gtest.h> |
| 63 | |
| 64 | int Add(int a, int b); // Provided by production code. |
| 65 | |
| 66 | TEST(CalculatorTest, AddsTwoNumbers) { |
| 67 | EXPECT_EQ(Add(2, 3), 5); |
| 68 | } |
| 69 | ``` |
| 70 | |
| 71 | ### Fixture (gtest) |
| 72 | |
| 73 | ```cpp |
| 74 | // tests/user_store_test.cpp |
| 75 | // Pseudocode stub: replace UserStore/User with project types. |
| 76 | #include <gtest/gtest.h> |
| 77 | #include <memory> |
| 78 | #include <optional> |
| 79 | #include <string> |
| 80 | |
| 81 | struct User { std::string name; }; |
| 82 | class UserStore { |
| 83 | public: |
| 84 | explicit UserStore(std::string /*path*/) {} |
| 85 | void Seed(std::initializer_list<User> /*users*/) {} |
| 86 | std::optional<User> Find(const std::string &/*name*/) { return User{"alice"}; } |
| 87 | }; |
| 88 | |
| 89 | class UserStoreTest : public ::testing::Test { |
| 90 | protected: |
| 91 | void SetUp() override { |
| 92 | store = std::make_unique<UserStore>(":memory:"); |
| 93 | store->Seed({{"alice"}, {"bob"}}); |
| 94 | } |
| 95 | |
| 96 | std::unique_ptr<UserStore> store; |
| 97 | }; |
| 98 | |
| 99 | TEST_F(UserStoreTest, FindsExistingUser) { |
| 100 | auto user = store->Find("alice"); |
| 101 | ASSERT_TRUE(user.has_value()); |
| 102 | EXPECT_EQ(user->name, "alice"); |
| 103 | } |
| 104 | ``` |
| 105 | |
| 106 | ### Mock (gmock) |
| 107 | |
| 108 | ```cpp |
| 109 | // tests/notifier_test.cpp |
| 110 | #include <gmock/gmock.h> |
| 111 | #include <gtest/gtest.h> |
| 112 | #include <string> |
| 113 | |
| 114 | class Notifier { |
| 115 | public: |
| 116 | virtual ~Notifier() = default; |
| 117 | virtual void Send(const std::string &message) = 0; |
| 118 | }; |
| 119 | |
| 120 | class MockNotifier : public Notifier { |
| 121 | public: |
| 122 | MOCK_METHOD(void, Send, (const std::string &message), (override)); |
| 123 | }; |
| 124 | |
| 125 | class Service { |
| 126 | public: |
| 127 | explicit Service(Notifier ¬ifier) : notifier_(notifier) {} |
| 128 | void Publish(const std::string &message) { notifier_.Send(message); } |
| 129 | |
| 130 | private: |
| 131 | Notifier ¬ifier_; |
| 132 | }; |
| 133 | |
| 134 | TEST(ServiceTest, SendsNotifications) { |
| 135 | MockNotifier notifier; |
| 136 | Service service(notifier); |
| 137 | |
| 138 | EXPECT_CALL(notifier, Send("hello")).Times(1); |
| 139 | service.Publish("hello"); |
| 140 | } |
| 141 | ``` |
| 142 | |
| 143 | ### CMake/CTest Quickstart |
| 144 | |
| 145 | ```cmake |
| 146 | # CMakeLists.txt (excerpt) |
| 147 | cmake_minimum_required(VERSION 3.20) |
| 148 | project(example LANGUAGES CXX) |
| 149 | |
| 150 | set(CMAKE_CXX_STANDARD 20) |
| 151 | set(CMAKE_CXX_STANDARD_REQUIRED ON) |
| 152 | |
| 153 | include(FetchContent) |
| 154 | # Prefer project-locked versions. If using a tag, use a pinned version per project policy. |
| 155 | set(GTEST_VERSION v1.17.0) # Adjust to project policy. |
| 156 | FetchContent_Declare( |
| 157 | googletest |
| 158 | # Google Test framework (official repository) |
| 159 | URL https://github.com/google/googletest/archive/refs/tags/${GTEST_VERSION}.zip |
| 160 | ) |
| 161 | FetchContent_MakeAvailable(googletest) |
| 162 | |
| 163 | add_executable(example_tests |
| 164 | tests/calculator_test.cpp |
| 165 | src/calculator.cpp |
| 166 | ) |
| 167 | target_link_libraries(example_tests GTest::gtest GTest::gmock GTest::gtest_main) |
| 168 | |
| 169 | enable_testing() |
| 170 | include(GoogleTest) |
| 171 | gtest_discover_tests(example_tests) |
| 172 | ``` |
| 173 | |
| 174 | ```bash |
| 175 | cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug |
| 176 | cmake --build build -j |
| 177 | ctest --test-dir build --output-on-failure |
| 178 | ``` |
| 179 | |
| 180 | ## Running Tests |
| 181 | |
| 182 | ```bash |
| 183 | ctest --test-dir build --output-on-failure |
| 184 | ctest --test-dir build -R ClampTest |
| 185 | ctest --test-dir build -R "UserStoreTest.*" --output-on-failure |
| 186 | ``` |
| 187 | |
| 188 | ```bash |
| 189 | ./build/example_tests --gtest_filter=ClampTest.* |
| 190 | ./build/example_tests --gtest_filter=UserStoreTest.FindsExistingUser |
| 191 | ``` |
| 192 | |
| 193 | ## De |