Modern C++ Testing
Google Test (gtest) is the most widely-used C++ testing framework, providing comprehensive features for unit testing, integration testing, and test-driven development.
Basic Test Structure
#include <gtest/gtest.h>
// Simple function to test
int add(int a, int b) {
return a + b;
}
// TEST(TestSuiteName, TestName)
TEST(MathTests, Addition) {
EXPECT_EQ(add(2, 3), 5);
EXPECT_EQ(add(-1, 1), 0);
EXPECT_EQ(add(0, 0), 0);
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Assertions
EXPECT vs ASSERT
- EXPECT_*: Continues test after failure (non-fatal)
- ASSERT_*: Stops test after failure (fatal)
TEST(AssertionExample, DemonstratesExpectVsAssert) {
EXPECT_EQ(1, 1); // Passes
EXPECT_EQ(1, 2); // Fails, but continues
EXPECT_EQ(3, 3); // Still runs
}
TEST(AssertionExample, AssertStopsOnFailure) {
ASSERT_EQ(1, 1); // Passes
ASSERT_EQ(1, 2); // Fails, stops test
EXPECT_EQ(3, 3); // Never runs
}
Common Assertions
TEST(AssertionTypes, BooleanAssertions) {
EXPECT_TRUE(true);
EXPECT_FALSE(false);
}
TEST(AssertionTypes, ComparisonAssertions) {
EXPECT_EQ(10, 10); // Equal
EXPECT_NE(10, 20); // Not equal
EXPECT_LT(5, 10); // Less than
EXPECT_LE(5, 10); // Less or equal
EXPECT_GT(10, 5); // Greater than
EXPECT_GE(10, 5); // Greater or equal
}
TEST(AssertionTypes, StringAssertions) {
EXPECT_STREQ("hello", "hello"); // C-strings equal
EXPECT_STRNE("hello", "world"); // C-strings not equal
EXPECT_STRCASEEQ("Hello", "hello"); // Case-insensitive
}
TEST(AssertionTypes, FloatingPointAssertions) {
EXPECT_FLOAT_EQ(1.0f, 1.0001f); // Within 4 ULPs
EXPECT_DOUBLE_EQ(1.0, 1.0001); // Within 4 ULPs
EXPECT_NEAR(1.0, 1.1, 0.2); // Within absolute error
}
Test Fixtures
Reuse setup and teardown code:
class DatabaseTest : public ::testing::Test {
protected:
void SetUp() override {
// Runs before each test
db = std::make_unique<Database>();
db->connect("test.db");
}
void TearDown() override {
// Runs after each test
db->disconnect();
}
std::unique_ptr<Database> db;
};
TEST_F(DatabaseTest, InsertRecord) {
EXPECT_TRUE(db->insert("key", "value"));
EXPECT_EQ(db->get("key"), "value");
}
TEST_F(DatabaseTest, DeleteRecord) {
db->insert("key", "value");
EXPECT_TRUE(db->remove("key"));
EXPECT_FALSE(db->contains("key"));
}
Static Setup/Teardown
Runs once per test suite:
class ExpensiveSetupTest : public ::testing::Test {
protected:
static void SetUpTestSuite() {
// Runs once before all tests in suite
shared_resource = new Resource();
}
static void TearDownTestSuite() {
// Runs once after all tests in suite
delete shared_resource;
}
static Resource* shared_resource;
};
Resource* ExpensiveSetupTest::shared_resource = nullptr;
Parameterized Tests
Test with multiple inputs:
class PrimeTest : public ::testing::TestWithParam<int> {
};
TEST_P(PrimeTest, IsPrime) {
int n = GetParam();
EXPECT_TRUE(is_prime(n));
}
INSTANTIATE_TEST_SUITE_P(
PrimeNumbers,
PrimeTest,
::testing::Values(2, 3, 5, 7, 11, 13, 17, 19)
);
With multiple parameters:
class AdditionTest : public ::testing::TestWithParam<std::tuple<int, int, int>> {
};
TEST_P(AdditionTest, ValidateAddition) {
auto [a, b, expected] = GetParam();
EXPECT_EQ(add(a, b), expected);
}
INSTANTIATE_TEST_SUITE_P(
AddCases,
AdditionTest,
::testing::Values(
std::make_tuple(1, 2, 3),
std::make_tuple(-1, 1, 0),
std::make_tuple(100, 200, 300)
)
);
Death Tests
Test for expected crashes or aborts:
void fatal_error(bool condition) {
if (condition) {
std::abort();
}
}
TEST(DeathTest, FatalError) {
EXPECT_DEATH(fatal_error(true), "");
EXPECT_EXIT(std::exit(42), ::testing::ExitedWithCode(42), "");
}
Exception Tests
TEST(ExceptionTest, ThrowsException) {
EXPECT_THROW(risky_function(), std::runtime_error);
EXPECT_ANY_THROW(another_risky_function());
EXPECT_NO_THROW(safe_function());
}
TEST(ExceptionTest, SpecificException) {
try {
risky_function();
FAIL() << "Expected exception";
} catch (const std::runtime_error& e) {
EXPECT_STREQ(e.what(), "Expected error message");
}
}
Custom Matchers
#include <gmock/gmock.h>
using ::testing::MatchesRegex;
using ::testing::StartsWith;
using ::testing::EndsWith;
using ::testing::HasSubstr;
TEST(StringMatchers, RegexAndSubstrings) {
std::string text = "Hello, World!";
EXPECT_THAT(text, StartsWith("Hello"));
EXPECT_THAT(text, EndsWith("World!"));
EXPECT_THAT(text, HasSubstr("lo, Wo"));
EXPECT_THAT(text, MatchesRegex("H.*!"));
}
Container matchers:
using ::testing::ElementsAre;
using ::testing::UnorderedElementsAre;
using ::testing::Contains;
using ::testing::SizeIs;
TEST(ContainerMatchers, VectorMatching) {
std::vector<int> vec{1, 2, 3, 4, 5};
EXPECT_THAT(vec, ElementsAre(1, 2, 3, 4, 5));
EXPECT_THAT(vec, Contains(3));
EXPECT_THAT(vec, SizeIs(5));
}
Mocking with Google Mock
#include <gmock/gmock.h>
// Interface to mock
class Database {
public:
virtual ~Database() = default;
virtual bool insert(const std::string& key, const std::string& value) = 0;
virtual std::string get(const std::string& key) = 0;
};
// Mock implementation
class MockDatabase : public Database {
public:
MOCK_METHOD(bool, insert, (const std::string& key, const std::string& value), (override));
MOCK_METHOD(std::string, get, (const std::string& key), (override));
};
// Class under test
class UserManager {
Database* db_;
public:
UserManager(Database* db) : db_(db) {}
bool addUser(const std::string& name) {
return db_->insert(name, "default_value");
}
};
TEST(UserManagerTest, AddsUser) {
MockDatabase mock_db;
UserManager manager(&mock_db);
// Set expectations
EXPECT_CALL(mock_db, insert("Alice", "default_value"))
.WillOnce(::testing::Return(true));
EXPECT_TRUE(manager.addUser("Alice"));
}
Mock Expectations
using ::testing::Return;
using ::testing::_;
using ::testing::AtLeast;
TEST(MockTest, Expectations) {
MockDatabase mock_db;
// Expect specific call
EXPECT_CALL(mock_db, get("key"))
.WillOnce(Return("value"));
// Expect any string argument
EXPECT_CALL(mock_db, get(_))
.WillRepeatedly(Return("default"));
// Expect at least 2 calls
EXPECT_CALL(mock_db, insert(_, _))
.Times(AtLeast(2))
.WillRepeatedly(Return(true));
}
Test Organization
namespace { // Anonymous namespace for test-only code
class CalculatorTest : public ::testing::Test {
protected:
Calculator calc;
};
TEST_F(CalculatorTest, Addition) {
EXPECT_EQ(calc.add(2, 3), 5);
}
TEST_F(CalculatorTest, Subtraction) {
EXPECT_EQ(calc.subtract(5, 3), 2);
}
} // namespace
Running Tests
Command Line
# Run all tests
./test_binary
# Run specific test
./test_binary --gtest_filter=MathTests.Addition
# Run tests matching pattern
./test_binary --gtest_filter=Math*
# Repeat tests
./test_binary --gtest_repeat=10
# Shuffle test order
./test_binary --gtest_shuffle
# Generate XML output
./test_binary --gtest_output=xml:test_results.xml
CMake Integration
enable_testing()
find_package(GTest REQUIRED)
add_executable(my_tests test_main.cpp test_math.cpp)
target_link_libraries(my_tests PRIVATE GTest::gtest_main)
include(GoogleTest)
gtest_discover_tests(my_tests)
Run with CTest:
ctest --output-on-failure
Test-Driven Development (TDD)
- Write failing test:
TEST(StringUtils, Reverse) {
EXPECT_EQ(reverse("hello"), "olleh"); // Test fails - function doesn't exist
}
- Implement minimum code to pass:
std::string reverse(const std::string& str) {
return std::string(str.rbegin(), str.rend());
}
- Refactor:
std::string reverse(std::string_view str) {
return std::string(str.rbegin(), str.rend());
}
Best Practices
- One assertion per test (when possible) - easier to identify failures
- Descriptive test names - document what’s being tested
- Arrange-Act-Assert pattern - structure test clearly
- Fast tests - avoid I/O when possible
- Isolated tests - no dependencies between tests
- Mock external dependencies - test in isolation
- Test edge cases - empty input, null, overflow
- Continuous integration - run tests automatically
Conceptual Check
What's the difference between EXPECT and ASSERT macros?
Interactive Lab
Complete the Code
// Create a test fixture for a Stack class class StackTest : public ::testing:: { protected: Stack<int> stack; };