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
enum class EmailError { Empty, InvalidFormat };
std::expected
Step 2: Write Tests (RED)
cpp
// validator/email_test.cpp
#include
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-failure1/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
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.infovalidator/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 buildRun tests
cmake --build build && ctest --test-dir buildGenerate 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) overASSERT_*(stops) when appropriate - Test behavior, not implementation details
- Include edge cases (empty, null, max values, boundary conditions)
- 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 implementation/verify- Run full verification loop
Related
- Skill:
skills/cpp-testing/ - Skill:
skills/tdd-workflow/