cpp-testing

Modern C++ testing workflow using GoogleTest/GoogleMock with CMake/CTest integration. Covers unit and integration test design with TDD red-green-refactor loop, fixtures, mocks, and fakes for dependency isolation Includes CMake/CTest configuration with automatic test discovery via gtest_discover_tests() and filtering strategies Provides coverage setup for GCC (gcov/lcov) and Clang (llvm-cov), plus sanitizer configuration (ASan, UBSan, TSan) for memory and race detection Documents flaky test prevention patterns, debugging workflows, and common pitfalls like global state, sleep-based synchronization, and over-mocking

INSTALLATION
npx skills add https://github.com/affaan-m/everything-claude-code --skill cpp-testing
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

C++ Testing (Agent Skill)

Agent-focused testing workflow for modern C++ (C++17/20) using GoogleTest/GoogleMock with CMake/CTest.

When to Use

  • Writing new C++ tests or fixing existing tests
  • Designing unit/integration test coverage for C++ components
  • Adding test coverage, CI gating, or regression protection
  • Configuring CMake/CTest workflows for consistent execution
  • Investigating test failures or flaky behavior
  • Enabling sanitizers for memory/race diagnostics

When NOT to Use

  • Implementing new product features without test changes
  • Large-scale refactors unrelated to test coverage or failures
  • Performance tuning without test regressions to validate
  • Non-C++ projects or non-test tasks

Core Concepts

  • TDD loop: red → green → refactor (tests first, minimal fix, then cleanups).
  • Isolation: prefer dependency injection and fakes over global state.
  • Test layout: tests/unit, tests/integration, tests/testdata.
  • Mocks vs fakes: mock for interactions, fake for stateful behavior.
  • CTest discovery: use gtest_discover_tests() for stable test discovery.
  • CI signal: run subset first, then full suite with --output-on-failure.

TDD Workflow

Follow the RED → GREEN → REFACTOR loop:

  • RED: write a failing test that captures the new behavior
  • GREEN: implement the smallest change to pass
  • REFACTOR: clean up while tests stay green
// tests/add_test.cpp

#include <gtest/gtest.h>

int Add(int a, int b); // Provided by production code.

TEST(AddTest, AddsTwoNumbers) { // RED

  EXPECT_EQ(Add(2, 3), 5);

}

// src/add.cpp

int Add(int a, int b) { // GREEN

  return a + b;

}

// REFACTOR: simplify/rename once tests pass

Code Examples

Basic Unit Test (gtest)

// tests/calculator_test.cpp

#include <gtest/gtest.h>

int Add(int a, int b); // Provided by production code.

TEST(CalculatorTest, AddsTwoNumbers) {

    EXPECT_EQ(Add(2, 3), 5);

}

Fixture (gtest)

// tests/user_store_test.cpp

// Pseudocode stub: replace UserStore/User with project types.

#include <gtest/gtest.h>

#include <memory>

#include <optional>

#include <string>

struct User { std::string name; };

class UserStore {

public:

    explicit UserStore(std::string /*path*/) {}

    void Seed(std::initializer_list<User> /*users*/) {}

    std::optional<User> Find(const std::string &#x26;/*name*/) { return User{"alice"}; }

};

class UserStoreTest : public ::testing::Test {

protected:

    void SetUp() override {

        store = std::make_unique<UserStore>(":memory:");

        store->Seed({{"alice"}, {"bob"}});

    }

    std::unique_ptr<UserStore> store;

};

TEST_F(UserStoreTest, FindsExistingUser) {

    auto user = store->Find("alice");

    ASSERT_TRUE(user.has_value());

    EXPECT_EQ(user->name, "alice");

}

Mock (gmock)

// tests/notifier_test.cpp

#include <gmock/gmock.h>

#include <gtest/gtest.h>

#include <string>

class Notifier {

public:

    virtual ~Notifier() = default;

    virtual void Send(const std::string &#x26;message) = 0;

};

class MockNotifier : public Notifier {

public:

    MOCK_METHOD(void, Send, (const std::string &#x26;message), (override));

};

class Service {

public:

    explicit Service(Notifier &#x26;notifier) : notifier_(notifier) {}

    void Publish(const std::string &#x26;message) { notifier_.Send(message); }

private:

    Notifier &#x26;notifier_;

};

TEST(ServiceTest, SendsNotifications) {

    MockNotifier notifier;

    Service service(notifier);

    EXPECT_CALL(notifier, Send("hello")).Times(1);

    service.Publish("hello");

}

CMake/CTest Quickstart

# CMakeLists.txt (excerpt)

cmake_minimum_required(VERSION 3.20)

project(example LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)

set(CMAKE_CXX_STANDARD_REQUIRED ON)

include(FetchContent)

# Prefer project-locked versions. If using a tag, use a pinned version per project policy.

set(GTEST_VERSION v1.17.0) # Adjust to project policy.

FetchContent_Declare(

  googletest

  # Google Test framework (official repository)

  URL https://github.com/google/googletest/archive/refs/tags/${GTEST_VERSION}.zip

)

FetchContent_MakeAvailable(googletest)

add_executable(example_tests

  tests/calculator_test.cpp

  src/calculator.cpp

)

target_link_libraries(example_tests GTest::gtest GTest::gmock GTest::gtest_main)

enable_testing()

include(GoogleTest)

gtest_discover_tests(example_tests)
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug

cmake --build build -j

ctest --test-dir build --output-on-failure

Running Tests

ctest --test-dir build --output-on-failure

ctest --test-dir build -R ClampTest

ctest --test-dir build -R "UserStoreTest.*" --output-on-failure
./build/example_tests --gtest_filter=ClampTest.*

./build/example_tests --gtest_filter=UserStoreTest.FindsExistingUser

Debugging Failures

  • Re-run the single failing test with gtest filter.
  • Add scoped logging around the failing assertion.
  • Re-run with sanitizers enabled.
  • Expand to full suite once the root cause is fixed.

Coverage

Prefer target-level settings instead of global flags.

option(ENABLE_COVERAGE "Enable coverage flags" OFF)

if(ENABLE_COVERAGE)

  if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")

    target_compile_options(example_tests PRIVATE --coverage)

    target_link_options(example_tests PRIVATE --coverage)

  elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")

    target_compile_options(example_tests PRIVATE -fprofile-instr-generate -fcoverage-mapping)

    target_link_options(example_tests PRIVATE -fprofile-instr-generate)

  endif()

endif()

GCC + gcov + lcov:

cmake -S . -B build-cov -DENABLE_COVERAGE=ON

cmake --build build-cov -j

ctest --test-dir build-cov

lcov --capture --directory build-cov --output-file coverage.info

lcov --remove coverage.info '/usr/*' --output-file coverage.info

genhtml coverage.info --output-directory coverage

Clang + llvm-cov:

cmake -S . -B build-llvm -DENABLE_COVERAGE=ON -DCMAKE_CXX_COMPILER=clang++

cmake --build build-llvm -j

LLVM_PROFILE_FILE="build-llvm/default.profraw" ctest --test-dir build-llvm

llvm-profdata merge -sparse build-llvm/default.profraw -o build-llvm/default.profdata

llvm-cov report build-llvm/example_tests -instr-profile=build-llvm/default.profdata

Sanitizers

option(ENABLE_ASAN "Enable AddressSanitizer" OFF)

option(ENABLE_UBSAN "Enable UndefinedBehaviorSanitizer" OFF)

option(ENABLE_TSAN "Enable ThreadSanitizer" OFF)

if(ENABLE_ASAN)

  add_compile_options(-fsanitize=address -fno-omit-frame-pointer)

  add_link_options(-fsanitize=address)

endif()

if(ENABLE_UBSAN)

  add_compile_options(-fsanitize=undefined -fno-omit-frame-pointer)

  add_link_options(-fsanitize=undefined)

endif()

if(ENABLE_TSAN)

  add_compile_options(-fsanitize=thread)

  add_link_options(-fsanitize=thread)

endif()

Flaky Tests Guardrails

  • Never use sleep for synchronization; use condition variables or latches.
  • Make temp directories unique per test and always clean them.
  • Avoid real time, network, or filesystem dependencies in unit tests.
  • Use deterministic seeds for randomized inputs.

Best Practices

DO

  • Keep tests deterministic and isolated
  • Prefer dependency injection over globals
  • Use ASSERT_* for preconditions, EXPECT_* for multiple checks
  • Separate unit vs integration tests in CTest labels or directories
  • Run sanitizers in CI for memory and race detection

DON'T

  • Don't depend on real time or network in unit tests
  • Don't use sleeps as synchronization when a condition variable can be used
  • Don't over-mock simple value objects
  • Don't use brittle string matching for non-critical logs

Common Pitfalls

  • Using fixed temp paths → Generate unique temp directories per test and clean them.
  • Relying on wall clock time → Inject a clock or use fake time sources.
  • Flaky concurrency tests → Use condition variables/latches and bounded waits.
  • Hidden global state → Reset global state in fixtures or remove globals.
  • Over-mocking → Prefer fakes for stateful behavior and only mock interactions.
  • Missing sanitizer runs → Add ASan/UBSan/TSan builds in CI.
  • Coverage on debug-only builds → Ensure coverage targets use consistent flags.

Optional Appendix: Fuzzing / Property Testing

Only use if the project already supports LLVM/libFuzzer or a property-testing library.

  • libFuzzer: best for pure functions with minimal I/O.
  • RapidCheck: property-based tests to validate invariants.

Minimal libFuzzer harness (pseudocode: replace ParseConfig):

#include <cstddef>

#include <cstdint>

#include <string>

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {

    std::string input(reinterpret_cast<const char *>(data), size);

    // ParseConfig(input); // project function

    return 0;

}

Alternatives to GoogleTest

  • Catch2: header-only, expressive matchers
  • doctest: lightweight, minimal compile overhead
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card