Cpp Testing

作者 affaan-mef648e01899b无许可证275K 个星标收录于 2026年10月8日更新于 2026年10月8日仓库3天前更新

Use only when writing/updating/fixing C++ tests, configuring GoogleTest/CTest, diagnosing failing or flaky tests, or adding coverage/sanitizers.

AI 生成的概览

指导使用 GoogleTest、CMake/CTest、覆盖率与 sanitizer 编写、修复和调试现代 C++ 测试。

功能
该技能为 C++17/20 测试提供面向智能体的工作流,基于 GoogleTest/GoogleMock 与 CMake/CTest。内容涵盖红-绿-重构的 TDD 循环、测试目录布局、fixture、mock 与 fake 的取舍、CTest 测试发现、测试运行与过滤、失败调试、使用 gcov/lcov 或 llvm-cov 的覆盖率,以及 sanitizer 构建。还列出易波动测试的防护规则、最佳实践、常见陷阱,以及可选的模糊测试或属性测试说明。
适用场景
适用于编写、更新或修复 C++ 测试,设计单元或集成测试覆盖,配置 CMake/CTest,或排查失败与不稳定的测试。也适合添加覆盖率、CI 门禁、回归保护,或进行内存与竞态问题的 sanitizer 诊断。
运行要求
仅为说明文档,不附带脚本。需要 C++17/20 工具链、CMake 3.20 或更高版本,以及 GoogleTest/GoogleMock(示例中通过 FetchContent 获取)。覆盖率与 sanitizer 部分需要 GCC 或 Clang 及 gcov/lcov 或 llvm-cov,获取依赖时可能需要网络访问。

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:

  1. RED: write a failing test that captures the new behavior
  2. GREEN: implement the smallest change to pass
  3. REFACTOR: clean up while tests stay green
cpp
// 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.cppint Add(int a, int b) { // GREEN  return a + b;}
// REFACTOR: simplify/rename once tests pass

Code Examples

Basic Unit Test (gtest)

cpp
// 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)

cpp
// 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 &/*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)

cpp
// 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 &message) = 0;};
class MockNotifier : public Notifier {public:    MOCK_METHOD(void, Send, (const std::string &message), (override));};
class Service {public:    explicit Service(Notifier &notifier) : notifier_(notifier) {}    void Publish(const std::string &message) { notifier_.Send(message); }
private:    Notifier &notifier_;};
TEST(ServiceTest, SendsNotifications) {    MockNotifier notifier;    Service service(notifier);
    EXPECT_CALL(notifier, Send("hello")).Times(1);    service.Publish("hello");}

CMake/CTest Quickstart

cmake
# 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)
bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debugcmake --build build -jctest --test-dir build --output-on-failure

Running Tests

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

Debugging Failures

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

Coverage

Prefer target-level settings instead of global flags.

cmake
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:

bash
cmake -S . -B build-cov -DENABLE_COVERAGE=ONcmake --build build-cov -jctest --test-dir build-covlcov --capture --directory build-cov --output-file coverage.infolcov --remove coverage.info '/usr/*' --output-file coverage.infogenhtml coverage.info --output-directory coverage

Clang + llvm-cov:

bash
cmake -S . -B build-llvm -DENABLE_COVERAGE=ON -DCMAKE_CXX_COMPILER=clang++cmake --build build-llvm -jLLVM_PROFILE_FILE="build-llvm/default.profraw" ctest --test-dir build-llvmllvm-profdata merge -sparse build-llvm/default.profraw -o build-llvm/default.profdatallvm-cov report build-llvm/example_tests -instr-profile=build-llvm/default.profdata

Sanitizers

cmake
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):

cpp
#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

来源与署名

来源:affaan-m/ecc位于.kiro/skills/cpp-testing提交ef648e0

许可证: 无许可证

内容归原作者所有。SourceWeft 从公开仓库中收录这些内容。

举报或申请下架