--- description: Enforce TDD workflow for C++. Write GoogleTest tests first, then implement. Verify coverage with gcov/lcov. --- # C++ TDD Command This command enforces test-driven development methodology for C++ code using GoogleTest/GoogleMock with CMake/CTest. ## What This Command Does 1. **Define Interfaces**: Scaffold class/function signatures first 2. **Write Tests**: Create comprehensive GoogleTest test cases (RED) 3. **Run Tests**: Verify tests fail for the right reason 4. **Implement Code**: Write minimal code to pass (GREEN) 5. **Refactor**: Improve while keeping tests green 6. **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 ```cpp TEST(SuiteName, TestName) { EXPECT_EQ(add(2, 3), 5); EXPECT_NE(result, nullptr); EXPECT_TRUE(is_valid); EXPECT_THROW(func(), std::invalid_argument); } ``` ### Fixtures ```cpp class DatabaseTest : public ::testing::Test { protected: void SetUp() override { db_ = create_test_db(); } void TearDown() override { db_.reset(); } std::unique_ptr db_; }; TEST_F(DatabaseTest, InsertsRecord) { db_->insert("key", "value"); EXPECT_EQ(db_->get("key"), "value"); } ``` ### Parameterized Tests ```cpp class PrimeTest : public ::testing::TestWithParam> {}; 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 ```bash # 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/`