cpp-test
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
- 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 testGREEN → Implement minimal code to passREFACTOR → Improve code, tests stay greenREPEAT → Next test caseExample 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 <string>#include <expected>
enum class EmailError { Empty, InvalidFormat};
std::expected<void, EmailError> validate_email(const std::string& email);```
## Step 2: Write Tests (RED)
```cpp// validator/email_test.cpp#include <gtest/gtest.h>#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 <regex>
std::expected<void, EmailError> 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 coveragecmake -DCMAKE_CXX_FLAGS="--coverage" -DCMAKE_EXE_LINKER_FLAGS="--coverage" -B build
# Run testscmake --build build && ctest --test-dir build
# Generate coverage reportlcov --capture --directory build --output-file coverage.infolcov --remove coverage.info '/usr/*' --output-file coverage.infogenhtml coverage.info --output-directory coverage_htmlCoverage 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) overASSERT_*(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
sleepin tests - Ignore flaky tests
Related Commands
/cpp-build- Fix build errors/cpp-review- Review code after implementationverification-loopskill - Run full verification loop
Related
- Skill:
skills/cpp-testing/ - Skill:
skills/tdd-workflow/