Search Knowledge

© 2026 LIBREUNI PROJECT

Modern C++ Programming / Tools and Practices

Testing with GoogleTest

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)

  1. Write failing test:
TEST(StringUtils, Reverse) {
    EXPECT_EQ(reverse("hello"), "olleh");  // Test fails - function doesn't exist
}
  1. Implement minimum code to pass:
std::string reverse(const std::string& str) {
    return std::string(str.rbegin(), str.rend());
}
  1. Refactor:
std::string reverse(std::string_view str) {
    return std::string(str.rbegin(), str.rend());
}

Best Practices

  1. One assertion per test (when possible) - easier to identify failures
  2. Descriptive test names - document what’s being tested
  3. Arrange-Act-Assert pattern - structure test clearly
  4. Fast tests - avoid I/O when possible
  5. Isolated tests - no dependencies between tests
  6. Mock external dependencies - test in isolation
  7. Test edge cases - empty input, null, overflow
  8. 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;
};