cpp-testing-advanced
Advanced C++ testing patterns — parameterized tests, death tests, advanced gmock, matchers, test data factories, async/concurrent testing, CI/CD integration, Catch2, and anti-patterns. Use after mastering the basics in cpp-testing.
Best use case
cpp-testing-advanced is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Advanced C++ testing patterns — parameterized tests, death tests, advanced gmock, matchers, test data factories, async/concurrent testing, CI/CD integration, Catch2, and anti-patterns. Use after mastering the basics in cpp-testing.
Teams using cpp-testing-advanced should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/cpp-testing-advanced/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How cpp-testing-advanced Compares
| Feature / Agent | cpp-testing-advanced | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Advanced C++ testing patterns — parameterized tests, death tests, advanced gmock, matchers, test data factories, async/concurrent testing, CI/CD integration, Catch2, and anti-patterns. Use after mastering the basics in cpp-testing.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# C++ Testing — Advanced Patterns
Advanced GoogleTest/GoogleMock patterns for teams with the basics established. For first-time setup, test fixtures, basic mocks, and sanitizers, see `cpp-testing` first.
## When to Use
- Parameterized or type-parameterized tests across many inputs
- Advanced mock expectations (InSequence, DoAll, SaveArg, Throw)
- Async or concurrent code verification
- CI/CD with sanitizer matrix builds
- Property-based or fuzz testing
- Catch2 as an alternative to GoogleTest
## 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;
}
```
## Parameterized Tests
Test the same logic across many inputs without duplication:
```cpp
// Value-parameterized test
class ClampTest : public ::testing::TestWithParam<std::tuple<int, int, int, int>> {};
TEST_P(ClampTest, ClampsBetweenMinAndMax) {
auto [value, lo, hi, expected] = GetParam();
EXPECT_EQ(Clamp(value, lo, hi), expected);
}
INSTANTIATE_TEST_SUITE_P(ClampValues, ClampTest,
::testing::Values(
std::make_tuple(-5, 0, 10, 0),
std::make_tuple( 5, 0, 10, 5),
std::make_tuple(15, 0, 10, 10),
std::make_tuple( 0, 0, 0, 0)
)
);
// Type-parameterized tests — run the same test for multiple types
template <typename T>
class NumericTest : public ::testing::Test {};
using NumericTypes = ::testing::Types<int, long, float, double>;
TYPED_TEST_SUITE(NumericTest, NumericTypes);
TYPED_TEST(NumericTest, DefaultIsZero) {
TypeParam value{};
EXPECT_EQ(value, static_cast<TypeParam>(0));
}
```
## Death Tests
Verify precondition violations and assertions:
```cpp
void divide(int a, int b) {
assert(b != 0 && "Division by zero");
// ...
}
TEST(DivideTest, AssertsOnZeroDivisor) {
EXPECT_DEATH(divide(10, 0), "Division by zero");
}
// EXPECT_DEATH — continues on failure
// ASSERT_DEATH — stops test on failure
// For functions that should throw instead
TEST(ParseTest, ThrowsOnInvalidInput) {
EXPECT_THROW(parse("!!!"), std::invalid_argument);
EXPECT_NO_THROW(parse("valid"));
}
```
## Advanced gmock
```cpp
using ::testing::_;
using ::testing::An;
using ::testing::AtLeast;
using ::testing::DoAll;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::SetArgPointee;
using ::testing::Throw;
class MockDatabase : public Database {
public:
MOCK_METHOD(bool, Connect, (const std::string& url), (override));
MOCK_METHOD(std::vector<Row>, Query, (const std::string& sql), (override));
MOCK_METHOD(void, Execute, (const std::string& sql), (override));
};
// Ordered expectations
TEST(ServiceTest, ConnectsThenQueries) {
MockDatabase db;
InSequence seq; // Enforces call order
EXPECT_CALL(db, Connect(_)).Times(1).WillOnce(Return(true));
EXPECT_CALL(db, Query("SELECT 1")).Times(1).WillOnce(Return(std::vector<Row>{}));
Service svc(db);
svc.Run();
}
// Capture argument
TEST(ServiceTest, PassesCorrectQuery) {
MockDatabase db;
std::string captured_sql;
EXPECT_CALL(db, Execute(_))
.WillOnce(SaveArg<0>(&captured_sql));
Service svc(db);
svc.UpdateStatus(42, "active");
EXPECT_THAT(captured_sql, ::testing::HasSubstr("UPDATE"));
EXPECT_THAT(captured_sql, ::testing::HasSubstr("42"));
}
// DoAll — multiple actions
EXPECT_CALL(db, Query(_))
.WillOnce(DoAll(
SaveArg<0>(&captured_sql),
Return(fake_rows)
));
// Throw from mock
EXPECT_CALL(db, Connect(_))
.WillOnce(Throw(std::runtime_error("connection refused")));
// At least N times
EXPECT_CALL(db, Query(_)).Times(AtLeast(1));
```
## Matchers Reference
```cpp
using ::testing::AllOf;
using ::testing::AnyOf;
using ::testing::Contains;
using ::testing::Each;
using ::testing::ElementsAre;
using ::testing::Ge;
using ::testing::HasSubstr;
using ::testing::IsEmpty;
using ::testing::Le;
using ::testing::Not;
using ::testing::SizeIs;
using ::testing::StartsWith;
using ::testing::StrEq;
using ::testing::UnorderedElementsAre;
// Collection matchers
EXPECT_THAT(vec, ElementsAre(1, 2, 3));
EXPECT_THAT(vec, UnorderedElementsAre(3, 1, 2));
EXPECT_THAT(vec, Contains(42));
EXPECT_THAT(vec, Each(Ge(0)));
EXPECT_THAT(vec, SizeIs(5));
EXPECT_THAT(vec, IsEmpty());
// String matchers
EXPECT_THAT(str, StartsWith("prefix"));
EXPECT_THAT(str, HasSubstr("middle"));
EXPECT_THAT(str, StrEq("exact"));
// Combining matchers
EXPECT_THAT(value, AllOf(Ge(0), Le(100))); // 0 <= value <= 100
EXPECT_THAT(value, AnyOf(Eq(1), Eq(2)));
EXPECT_THAT(value, Not(IsEmpty()));
```
## Test Data Factories
```cpp
// Factory function — named after the test scenario
namespace test_data {
Market ActiveMarket(std::string_view slug = "test-market") {
return Market{
.id = 1,
.slug = std::string{slug},
.status = Market::Status::Active,
.created_at = std::chrono::system_clock::now(),
};
}
Market DraftMarket(std::string_view slug = "draft") {
auto m = ActiveMarket(slug);
m.status = Market::Status::Draft;
return m;
}
User AdminUser() {
return User{.id = 1, .role = User::Role::Admin, .name = "Admin"};
}
} // namespace test_data
// Usage
TEST(MarketServiceTest, CannotPublishAlreadyActiveMarket) {
auto market = test_data::ActiveMarket();
// ...
}
```
## Testing Async / Concurrent Code
```cpp
#include <gtest/gtest.h>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <mutex>
#include <thread>
// Use condition variables, not sleep
TEST(AsyncServiceTest, EventuallyPublishesEvent) {
std::mutex mu;
std::condition_variable cv;
bool received = false;
AsyncService svc;
svc.OnEvent([&](const Event& e) {
std::lock_guard<std::mutex> lock{mu};
received = true;
cv.notify_one();
});
svc.Trigger();
std::unique_lock<std::mutex> lock{mu};
bool signaled = cv.wait_for(lock, std::chrono::seconds{2},
[&] { return received; });
EXPECT_TRUE(signaled) << "Timed out waiting for event";
}
// Thread safety test with many concurrent operations
TEST(AtomicCounterTest, CorrectUnderConcurrency) {
AtomicCounter counter;
constexpr int kThreads = 10;
constexpr int kIncrementsPerThread = 1000;
std::vector<std::thread> threads;
for (int i = 0; i < kThreads; ++i) {
threads.emplace_back([&] {
for (int j = 0; j < kIncrementsPerThread; ++j) {
counter.Increment();
}
});
}
for (auto& t : threads) t.join();
EXPECT_EQ(counter.Value(), kThreads * kIncrementsPerThread);
}
```
## CI/CD Integration
```yaml
# .github/workflows/test.yml
name: C++ Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-24.04
strategy:
matrix:
compiler: [gcc, clang]
sanitizer: [none, asan, ubsan]
steps:
- uses: actions/checkout@v4
- name: Configure
run: |
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Debug \
-DENABLE_ASAN=${{ matrix.sanitizer == 'asan' && 'ON' || 'OFF' }} \
-DENABLE_UBSAN=${{ matrix.sanitizer == 'ubsan' && 'ON' || 'OFF' }} \
-DENABLE_COVERAGE=${{ matrix.sanitizer == 'none' && 'ON' || 'OFF' }}
- name: Build
run: cmake --build build -j$(nproc)
- name: Test
run: ctest --test-dir build --output-on-failure -j$(nproc)
- name: Coverage Report
if: matrix.sanitizer == 'none' && matrix.compiler == 'gcc'
run: |
lcov --capture --directory build --output-file coverage.info
lcov --remove coverage.info '/usr/*' --output-file coverage.info
genhtml coverage.info --output-directory coverage
```
## Catch2 Example (Alternative)
```cmake
# Catch2 with CMake FetchContent
FetchContent_Declare(
Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.7.1
)
FetchContent_MakeAvailable(Catch2)
add_executable(catch2_tests tests/catch2_tests.cpp)
target_link_libraries(catch2_tests PRIVATE Catch2::Catch2WithMain)
include(CTest)
include(Catch)
catch_discover_tests(catch2_tests)
```
```cpp
// tests/catch2_tests.cpp
#include <catch2/catch_all.hpp>
TEST_CASE("Add function", "[calculator]") {
SECTION("adds positive numbers") {
REQUIRE(Add(2, 3) == 5);
}
SECTION("handles negative numbers") {
REQUIRE(Add(-1, 1) == 0);
}
}
// Parameterized with GENERATE
TEST_CASE("Clamp function", "[calculator]") {
auto [value, lo, hi, expected] = GENERATE(table<int, int, int, int>({
{-5, 0, 10, 0},
{ 5, 0, 10, 5},
{15, 0, 10, 10},
}));
REQUIRE(Clamp(value, lo, hi) == expected);
}
```
## Anti-Patterns
### Using `sleep` for Async Synchronization
**Wrong:**
```cpp
TEST(AsyncServiceTest, EventuallyPublishesEvent) {
AsyncService svc;
svc.Trigger();
std::this_thread::sleep_for(std::chrono::milliseconds{200}); // arbitrary wait
EXPECT_TRUE(svc.DidPublish());
}
```
**Correct:**
```cpp
TEST(AsyncServiceTest, EventuallyPublishesEvent) {
std::mutex mu;
std::condition_variable cv;
bool received = false;
AsyncService svc;
svc.OnPublish([&] {
std::lock_guard lock{mu};
received = true;
cv.notify_one();
});
svc.Trigger();
std::unique_lock lock{mu};
EXPECT_TRUE(cv.wait_for(lock, std::chrono::seconds{2}, [&] { return received; }));
}
```
**Why:** Fixed sleeps are both slow and still flaky; condition variables provide a deterministic, bounded wait.
### Mocking Simple Value Objects
**Wrong:**
```cpp
class MockPoint : public Point {
public:
MOCK_METHOD(double, X, (), (const, override));
MOCK_METHOD(double, Y, (), (const, override));
};
TEST(DistanceTest, ComputesCorrectly) {
MockPoint a, b;
ON_CALL(a, X()).WillByDefault(Return(0.0));
ON_CALL(b, X()).WillByDefault(Return(3.0));
// ... overly complex setup for a trivial struct
}
```
**Correct:**
```cpp
TEST(DistanceTest, ComputesCorrectly) {
Point a{0.0, 0.0};
Point b{3.0, 4.0};
EXPECT_DOUBLE_EQ(Distance(a, b), 5.0);
}
```
**Why:** Mocking simple value types adds boilerplate with no isolation benefit; use real instances directly.
### Using a Fixed Temporary Directory Path
**Wrong:**
```cpp
TEST(FileParserTest, ParsesValidFile) {
std::ofstream out("/tmp/test_input.txt"); // shared path, collides between tests
out << "data";
out.close();
EXPECT_TRUE(ParseFile("/tmp/test_input.txt").ok());
}
```
**Correct:**
```cpp
TEST(FileParserTest, ParsesValidFile) {
auto tmp = std::filesystem::temp_directory_path() /
("test_" + std::to_string(::testing::UnitTest::GetInstance()->random_seed()));
std::filesystem::create_directories(tmp);
std::ofstream out(tmp / "input.txt");
out << "data";
out.close();
EXPECT_TRUE(ParseFile((tmp / "input.txt").string()).ok());
std::filesystem::remove_all(tmp);
}
```
**Why:** Shared temp paths cause test interference when tests run in parallel; use unique directories per test.
### Using `EXPECT_*` for Preconditions That Should Abort the Test
**Wrong:**
```cpp
TEST(UserStoreTest, FindsUser) {
auto user = store->Find("alice");
EXPECT_TRUE(user.has_value()); // test continues even if nullopt
EXPECT_EQ(user->name, "alice"); // dereferences nullopt — UB crash
}
```
**Correct:**
```cpp
TEST(UserStoreTest, FindsUser) {
auto user = store->Find("alice");
ASSERT_TRUE(user.has_value()); // stops test immediately on failure
EXPECT_EQ(user->name, "alice"); // only reached when user is valid
}
```
**Why:** `EXPECT_*` continues execution after failure; use `ASSERT_*` for preconditions whose failure would cause a crash or invalid state.
### Relying on Global State Between Tests
**Wrong:**
```cpp
static std::vector<std::string> g_log;
TEST(ServiceTest, LogsOnStart) {
Service svc;
svc.Start();
EXPECT_EQ(g_log.size(), 1);
}
TEST(ServiceTest, LogsOnStop) {
// g_log already has 1 entry from previous test — order-dependent
Service svc;
svc.Stop();
EXPECT_EQ(g_log.size(), 2);
}
```
**Correct:**
```cpp
class ServiceTest : public ::testing::Test {
protected:
void SetUp() override { g_log.clear(); } // reset before each test
std::vector<std::string> g_log;
};
TEST_F(ServiceTest, LogsOnStart) {
Service svc{g_log};
svc.Start();
EXPECT_EQ(g_log.size(), 1);
}
TEST_F(ServiceTest, LogsOnStop) {
Service svc{g_log};
svc.Stop();
EXPECT_EQ(g_log.size(), 1);
}
```
**Why:** Global state makes test execution order matter; fixtures with `SetUp`/`TearDown` ensure each test starts clean.
## Alternatives to GoogleTest
- **Catch2**: expressive syntax, BDD-style sections, built-in parameterized tests
- **doctest**: header-only, minimal compile overhead, compatible with GoogleTest assertionsRelated Skills
visual-testing
Visual Regression Testing: tool comparison (Chromatic/Percy/Playwright screenshots/BackstopJS), pixel-diff vs AI-based comparison, baseline management, flakiness strategies (masks, tolerances, waitForLoadState), CI integration with GitHub Actions, and Storybook integration.
typescript-testing
TypeScript testing patterns: Vitest for unit/integration, Playwright for E2E, MSW for API mocking, Testing Library for React components. Core TDD methodology for TypeScript/JavaScript projects.
typescript-patterns-advanced
Advanced TypeScript — mapped types, template literal types, conditional types, infer, type guards, decorators, async patterns, testing with Vitest/Jest, and performance. Extends typescript-patterns.
tdd-workflow-advanced
TDD anti-patterns — writing code before tests, testing implementation details instead of behavior, using waitForTimeout as a sync strategy, chaining tests that share state, mocking the system under test instead of its dependencies.
swift-testing
Swift testing patterns: Swift Testing framework (Swift 6+), XCTest for UI tests, async/await test cases, actor testing, Combine testing, and XCUITest for UI automation. TDD for Swift/SwiftUI.
swift-protocol-di-testing
Protocol-based dependency injection for testable Swift code — mock file system, network, and external APIs using focused protocols and Swift Testing.
swift-patterns-advanced
Advanced Swift patterns — property wrappers, result builders, Combine basics, opaque & existential types, macro system, advanced generics, and performance optimization. Extends swift-patterns.
serverless-patterns-advanced
Advanced Serverless patterns — Lambda idempotency (Lambda Powertools + DynamoDB persistence layer), Lambda cost model (pricing formula, break-even vs containers), and CloudWatch Insights observability queries for cold starts, duration, and errors.
security-review-advanced
Security anti-patterns — localStorage token storage (XSS risk), trusting client-side authorization checks, reflecting full error details to clients, blacklist vs whitelist input validation, using npm install instead of npm ci in CI pipelines.
scala-testing
Scala testing with ScalaTest, MUnit, and ScalaCheck: FunSpec/FlatSpec test structure, property-based testing with forAll, mocking with MockitoSugar, Cats Effect testing with munit-cats-effect (runTest/IOSuite), ZIO Test, Testcontainers-Scala for database integration tests, and CI integration with sbt. Use when writing or reviewing Scala tests.
rust-testing
Rust testing patterns — unit tests with mockall, integration tests with sqlx transactions, HTTP handler testing (axum), benchmarks (criterion), property tests (proptest), fuzzing, and CI with cargo-nextest.
rust-testing-advanced
Advanced Rust testing anti-patterns and corrections — cfg(test) placement, expect() over unwrap(), mockall expectation ordering, executor mixing (#[tokio::test] vs block_on), PgPool isolation with