옵션
집집 Skill 개발자 도구 cpp-testing

cpp-testing

affaan-m/ECC affaan-m/ECC

CMake/CTest와 함께 GoogleTest/GoogleMock을 사용하여 C++ 테스트를 작성, 수정 및 실행하며, 여기에는 TDD 워크플로, 커버리지 및 산티라이저가 포함됩니다.

...모든 것을 확장하십시오
0
업데이트 된 시간 2026년 10월 1일

C++ 테스트 (에이전트 스킬)

CMake/CTest와 GoogleTest/GoogleMock을 활용한 최신 C++(C++17/20)용 에이전트 중심 테스트 워크플로우.

사용 시점

  • 새로운 C++ 테스트를 작성하거나 기존 테스트를 수정할 때
  • C++ 구성 요소에 대한 단위/통합 테스트 커버리지 설계 시
  • 테스트 커버리지 추가, CI 게이트 설정 또는 회귀 테스트 보호
  • 일관된 실행을 위한 CMake/CTest 워크플로우 구성
  • 테스트 실패 또는 불안정한 동작 조사
  • 메모리/경합 진단용 산티라이저 활성화

사용하지 말아야 할 경우

  • 테스트 변경 없이 새로운 제품 기능 구현
  • 테스트 커버리지나 실패와 무관한 대규모 리팩토링
  • 검증할 테스트 회귀 현상 없이 성능 튜닝 수행
  • C++가 아닌 프로젝트 또는 테스트와 무관한 작업

핵심 개념

  • TDD 루프: 빨간색 → 녹색 → 리팩토링 (테스트 우선, 최소한의 수정, 그 다음 정리).
  • 격리: 전역 상태보다는 의존성 주입과 페이크를 우선적으로 사용한다.
  • 테스트 레이아웃: tests/unit, tests/integration, tests/testdata.
  • 모크(Mock) 대 페이크(Fake): 상호작용에는 모크를, 상태 기반 동작에는 페이크를 사용합니다.
  • CTest 탐색: gtest_discover_tests() 안정적인 테스트 탐색을 위해 사용하십시오.
  • CI 신호: 먼저 부분 테스트를 실행한 다음, --output-on-failure.

TDD 워크플로우

RED → GREEN → REFACTOR 루프를 따르세요:

  1. RED: 새로운 동작을 포착하는 실패하는 테스트 작성
  2. GREEN: 테스트를 통과하도록 최소한의 변경 사항만 구현
  3. 리팩토링: 테스트가 계속 통과되는 동안 코드를 정리합니다
// tests/add_test.cpp
#include 

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

코드 예시

기본 단위 테스트 (gtest)

// tests/calculator_test.cpp
#include 

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

TEST(CalculatorTest, AddsTwoNumbers) {
    EXPECT_EQ(Add(2, 3), 5);
}

픽스처 (gtest)

// tests/user_store_test.cpp
// Pseudocode stub: replace UserStore/User with project types.
#include 
#include 
#include 
#include 

struct User { std::string name; };
class UserStore {
public:
    explicit UserStore(std::string /*path*/) {}
    void Seed(std::initializer_list /*users*/) {}
    std::optional Find(const std::string &/*name*/) { return User{"alice"}; }
};

class UserStoreTest : public ::testing::Test {
protected:
    void SetUp() override {
        store = std::make_unique(":memory:");
        store->Seed({{"alice"}, {"bob"}});
    }

    std::unique_ptr store;
};

TEST_F(UserStoreTest, FindsExistingUser) {
    auto user = store->Find("alice");
    ASSERT_TRUE(user.has_value());
    EXPECT_EQ(user->name, "alice");
}

모크 (gmock)

// tests/notifier_test.cpp
#include 
#include 
#include 

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 ¬ifier) : notifier_(notifier) {}
    void Publish(const std::string &message) { notifier_.Send(message); }

private:
    Notifier ¬ifier_;
};

TEST(ServiceTest, SendsNotifications) {
    MockNotifier notifier;
    Service service(notifier);

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

CMake/CTest 빠른 시작

# 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

테스트 실행

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

실패 디버깅

  1. gtest 필터를 사용하여 실패한 단일 테스트를 다시 실행합니다.
  2. 실패한 어설션 주변에 범위 지정 로깅을 추가합니다.
  3. 산티라이저를 활성화한 상태로 다시 실행합니다.
  4. 근본 원인이 해결되면 전체 테스트 모음으로 확장하십시오.

커버리지

전역 플래그 대신 대상 수준 설정을 우선적으로 사용하십시오.

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

산티라이저

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()

불안정한 테스트 안전 장치

  • 절대 sleep 동기화를 위해 절대 사용하지 말고, 조건 변수나 래치를 사용하십시오.
  • 테스트마다 고유한 임시 디렉터리를 생성하고 항상 정리하십시오.
  • 단위 테스트에서 실시간, 네트워크 또는 파일 시스템 의존성을 피하십시오.
  • 무작위 입력에는 결정론적 시드를 사용하십시오.

모범 사례

해야 할 사항

  • 테스트를 결정론적이고 독립적으로 유지하십시오
  • 전역 변수보다는 의존성 주입을 선호하십시오
  • 사용 ASSERT_* 를 사용하여 전제 조건을 확인하고, EXPECT_* 여러 가지 검사가 필요한 경우
  • CTest 레이블이나 디렉터리에서 단위 테스트와 통합 테스트를 구분하십시오
  • 메모리 및 경합 현상 탐지를 위해 CI에서 산티라이저를 실행하십시오

하지 말 것

  • 단위 테스트에서 실시간 또는 네트워크에 의존하지 마세요
  • 조건 변수를 사용할 수 있는 경우 동기화를 위해 sleep을 사용하지 마십시오
  • 단순한 값 객체를 과도하게 모킹하지 마십시오
  • 중요하지 않은 로그에 대해 불안정한 문자열 일치 연산을 사용하지 마십시오

흔한 함정

  • 고정된 임시 경로 사용 → 테스트마다 고유한 임시 디렉터리를 생성하고 정리하십시오.
  • 실시간에 의존 → 시계를 주입하거나 가짜 시간 소스를 사용하십시오.
  • 불안정한 동시성 테스트 → 조건 변수/래치 및 유한 대기(bounded waits)를 사용하십시오.
  • 숨겨진 전역 상태 → 피처에서 전역 상태를 재설정하거나 전역 변수를 제거하십시오.
  • 과도한 모킹 → 상태 기반 동작에는 페이크를 사용하고, 상호작용만 모킹하도록 하십시오.
  • 산티라이저 실행 누락 → CI에 ASan/UBSan/TSan 빌드를 추가합니다.
  • 디버그 전용 빌드에서의 커버리지 → 커버리지 타깃이 일관된 플래그를 사용하도록 보장하십시오.

선택적 부록: 퍼징 / 속성 테스트

프로젝트가 이미 LLVM/libFuzzer 또는 속성 테스트 라이브러리를 지원하는 경우에만 사용하십시오.

  • libFuzzer: I/O가 최소한인 순수 함수에 가장 적합합니다.
  • RapidCheck: 불변 조건을 검증하는 속성 기반 테스트.

최소 libFuzzer 하네스 (의사코드: ParseConfig 대체):

#include 
#include 
#include 

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    std::string input(reinterpret_cast(data), size);
    // ParseConfig(input); // project function
    return 0;
}

GoogleTest의 대안

  • Catch2: 헤더만으로도 사용 가능하며 표현력이 뛰어난 매처
  • doctest: 가볍고 컴파일 오버헤드가 최소화됨
GitHub에서 보기
---
name: cpp-testing
description: Write, fix, and run C++ tests using GoogleTest/GoogleMock with CMake/CTest, including TDD workflows, coverage, and sanitizers.
---

# 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.cpp
int 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=Debug
cmake --build build -j
ctest --test-dir build --output-on-failure
```

## Running Tests

```bash
ctest --test-dir build --output-on-failure
ctest --test-dir build -R ClampTest
ctest --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=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:

```bash
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

```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

모든 파일

1개 파일

cpp-testing 설치

스킬 파일을 다운로드하여 .claude/skills/ 디렉터리에 압축을 풀어주세요.

ZIP 다운로드

저장소를 클론하고 스킬 파일을 프로젝트에 복사하세요.

git clone https://github.com/affaan-m/ECC/tree/main/skills/cpp-testing # Copy SKILL.md to your .claude/skills/ directory

복사 복사
빠른 설정: 스킬 폴더를 .claude/skills/로 복사하세요. Claude가 해당 스킬을 자동으로 감지하여 사용할 것입니다.
저장소 affaan-m/ECC

관련 스킬

algorithmic-art
업데이트 된 시간 2026년 8월 27일
systematic-debugging
업데이트 된 시간 2026년 9월 3일
tech-debt-tracker
업데이트 된 시간 2026년 8월 29일
continual-learning
업데이트 된 시간 2026년 9월 10일
OR