Skip to main content
Kodelyth ECC
Command

/cpp-test

Enforce TDD workflow for C++. Write GoogleTest tests first, then implement. Verify coverage with gcov/lcov.

Run:/cpp-test

C++ TDD Command

This command enforces test-driven development methodology for C++ code using GoogleTest/GoogleMock with CMake/CTest.

What This Command Does

  • Define Interfaces: Scaffold class/function signatures first
  • Write Tests: Create comprehensive GoogleTest test cases (RED)
  • Run Tests: Verify tests fail for the right reason
  • Implement Code: Write minimal code to pass (GREEN)
  • Refactor: Improve while keeping tests green
  • Check Coverage: Ensure 80%+ coverage

When to Use

Use /cpp-test when:

  • Implementing new C++ functions or classes
  • Adding test coverage to existing code
  • Fixing bugs (write failing test first)
  • Building critical business logic
  • Learning TDD workflow in C++

TDD Cycle

RED     → Write failing GoogleTest test
GREEN   → Implement minimal code to pass
REFACTOR → Improve code, tests stay green
REPEAT  → Next test case

Example Session

User: /cpp-test I need a function to validate email addresses

Agent:

TDD Session: Email Validator

Step 1: Define Interface

cpp // validator/email.hpp #pragma once #include #include

enum class EmailError { Empty, InvalidFormat };

std::expected validate_email(const std::string& email);


Step 2: Write Tests (RED)

cpp // validator/email_test.cpp #include #include "email.hpp"

TEST(ValidateEmail, AcceptsSimpleEmail) { auto result = validate_email("user@example.com"); EXPECT_TRUE(result.has_value()); }

TEST(ValidateEmail, AcceptsSubdomain) { EXPECT_TRUE(validate_email("user@mail.example.com").has_value()); }

TEST(ValidateEmail, AcceptsPlus) { EXPECT_TRUE(validate_email("user+tag@example.com").has_value()); }

TEST(ValidateEmail, RejectsEmpty) { auto result = validate_email(""); ASSERT_FALSE(result.has_value()); EXPECT_EQ(result.error(), EmailError::Empty); }

TEST(ValidateEmail, RejectsNoAtSign) { EXPECT_FALSE(validate_email("userexample.com").has_value()); }

TEST(ValidateEmail, RejectsNoDomain) { EXPECT_FALSE(validate_email("user@").has_value()); }

TEST(ValidateEmail, RejectsNoLocalPart) { EXPECT_FALSE(validate_email("@example.com").has_value()); }


Step 3: Run Tests - Verify FAIL

bash $ cmake --build build && ctest --test-dir build --output-on-failure

1/1 Test #1: email_validator_test .....***Failed --- undefined reference to validate_email

FAIL


✓ Tests fail as expected (unimplemented).

Step 4: Implement Minimal Code (GREEN)

cpp // validator/email.cpp #include "email.hpp" #include

std::expected validate_email(const std::string& email) { if (email.empty()) { return std::unexpected(EmailError::Empty); } static const std::regex pattern(R"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})"); if (!std::regex_match(email, pattern)) { return std::unexpected(EmailError::InvalidFormat); } return {}; }


Step 5: Run Tests - Verify PASS

bash $ cmake --build build && ctest --test-dir build --output-on-failure

1/1 Test #1: email_validator_test ..... Passed 0.01 sec

100% tests passed.


✓ All tests passing!

Step 6: Check Coverage

bash $ cmake -DCMAKE_CXX_FLAGS="--coverage" -B build && cmake --build build $ ctest --test-dir build $ lcov --capture --directory build --output-file coverage.info $ lcov --list coverage.info

validator/email.cpp | 100%


✓ Coverage: 100%

TDD Complete!

Test Patterns

Basic Tests

TEST(SuiteName, TestName) {
    EXPECT_EQ(add(2, 3), 5);
    EXPECT_NE(result, nullptr);
    EXPECT_TRUE(is_valid);
    EXPECT_THROW(func(), std::invalid_argument);
}

Fixtures

class DatabaseTest : public ::testing::Test {
protected:
    void SetUp() override { db_ = create_test_db(); }
    void TearDown() override { db_.reset(); }
    std::unique_ptr<Database> db_;
};

TEST_F(DatabaseTest, InsertsRecord) { db_->insert("key", "value"); EXPECT_EQ(db_->get("key"), "value"); }

Parameterized Tests

class PrimeTest : public ::testing::TestWithParam<std::pair<int, bool>> {};

TEST_P(PrimeTest, ChecksPrimality) { auto [input, expected] = GetParam(); EXPECT_EQ(is_prime(input), expected); }

INSTANTIATE_TEST_SUITE_P(Primes, PrimeTest, ::testing::Values( std::make_pair(2, true), std::make_pair(4, false), std::make_pair(7, true) ));

Coverage Commands

# Build with coverage
cmake -DCMAKE_CXX_FLAGS="--coverage" -DCMAKE_EXE_LINKER_FLAGS="--coverage" -B build

Run tests

cmake --build build && ctest --test-dir build

Generate coverage report

lcov --capture --directory build --output-file coverage.info lcov --remove coverage.info '/usr/*' --output-file coverage.info genhtml coverage.info --output-directory coverage_html

Coverage Targets

| Code Type | Target | |-----------|--------| | Critical business logic | 100% | | Public APIs | 90%+ | | General code | 80%+ | | Generated code | Exclude |

TDD Best Practices

DO:

  • Write test FIRST, before any implementation
  • Run tests after each change
  • Use EXPECT_* (continues) over ASSERT_* (stops) when appropriate
  • Test behavior, not implementation details
  • Include edge cases (empty, null, max values, boundary conditions)
DON'T:
  • Write implementation before tests
  • Skip the RED phase
  • Test private methods directly (test through public API)
  • Use sleep in tests
  • Ignore flaky tests

Related Commands

  • /cpp-build - Fix build errors
  • /cpp-review - Review code after implementation
  • /verify - Run full verification loop

Related

  • Skill: skills/cpp-testing/
  • Skill: skills/tdd-workflow/