Docs
README
Unit Testing with Google Test
Overview
Google Test (gtest) is the most popular C++ testing framework. It provides a rich set of assertions, test fixtures, and features for writing comprehensive unit tests.
Learning Objectives
By the end of this section, you will understand:
- •Setting up Google Test
- •Writing test cases and test suites
- •Using assertions and expectations
- •Creating test fixtures
- •Parameterized tests
- •Mocking with Google Mock
Why Unit Testing?
┌─────────────────────────────────────────────────────────────────────────┐
│ TESTING PYRAMID │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ▲ │
│ /│\ End-to-End Tests │
│ / │ \ (Few, Slow, Expensive) │
│ / │ \ │
│ /───┼───\ │
│ / │ \ Integration Tests │
│ / │ \ (Some, Medium Speed) │
│ /──────┼──────\ │
│ / │ \ │
│ / │ \ Unit Tests │
│ /─────────┼─────────\ (Many, Fast, Cheap) │
│ ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ │
│ │
│ Unit tests form the foundation - fast feedback, isolated tests │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Setting Up Google Test
Using FetchContent (Recommended)
cmake_minimum_required(VERSION 3.16)
project(MyProject)
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
)
FetchContent_MakeAvailable(googletest)
enable_testing()
add_executable(tests test_main.cpp)
target_link_libraries(tests GTest::gtest_main)
include(GoogleTest)
gtest_discover_tests(tests)
Installing on Linux
# Ubuntu/Debian
sudo apt install libgtest-dev
# Build if needed
cd /usr/src/gtest
sudo cmake .
sudo make
sudo cp lib/*.a /usr/lib
Basic Test Structure
#include <gtest/gtest.h>
// Simple test
TEST(TestSuiteName, TestName) {
// Arrange
int a = 2;
int b = 3;
// Act
int result = a + b;
// Assert
EXPECT_EQ(result, 5);
}
Assertions
┌─────────────────────────────────────────────────────────────────────────┐
│ ASSERTION TYPES │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ EXPECT_* : Non-fatal assertion (test continues on failure) │ │
│ │ ASSERT_* : Fatal assertion (test stops on failure) │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ Boolean: │
│ ───────────────────────────────────────────────── │
│ EXPECT_TRUE(condition) │
│ EXPECT_FALSE(condition) │
│ │
│ Comparison: │
│ ───────────────────────────────────────────────── │
│ EXPECT_EQ(val1, val2) // val1 == val2 │
│ EXPECT_NE(val1, val2) // val1 != val2 │
│ EXPECT_LT(val1, val2) // val1 < val2 │
│ EXPECT_LE(val1, val2) // val1 <= val2 │
│ EXPECT_GT(val1, val2) // val1 > val2 │
│ EXPECT_GE(val1, val2) // val1 >= val2 │
│ │
│ Floating Point: │
│ ───────────────────────────────────────────────── │
│ EXPECT_FLOAT_EQ(val1, val2) │
│ EXPECT_DOUBLE_EQ(val1, val2) │
│ EXPECT_NEAR(val1, val2, abs_error) │
│ │
│ String: │
│ ───────────────────────────────────────────────── │
│ EXPECT_STREQ(str1, str2) // C strings equal │
│ EXPECT_STRNE(str1, str2) // C strings not equal │
│ EXPECT_STRCASEEQ(str1, str2) // Case-insensitive equal │
│ │
│ Exception: │
│ ───────────────────────────────────────────────── │
│ EXPECT_THROW(statement, exception_type) │
│ EXPECT_ANY_THROW(statement) │
│ EXPECT_NO_THROW(statement) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Test Fixtures
Test fixtures allow you to share setup and teardown code between tests:
class CalculatorTest : public ::testing::Test {
protected:
Calculator calc; // Shared across tests
void SetUp() override {
// Called before each test
calc.clear();
}
void TearDown() override {
// Called after each test
}
};
TEST_F(CalculatorTest, Addition) {
EXPECT_EQ(calc.add(2, 3), 5);
}
TEST_F(CalculatorTest, Subtraction) {
EXPECT_EQ(calc.subtract(5, 3), 2);
}
Test Organization
┌─────────────────────────────────────────────────────────────────────────┐
│ TEST ORGANIZATION │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Project │
│ └── tests/ │
│ ├── CMakeLists.txt │
│ ├── main.cpp (if not using gtest_main) │
│ ├── test_calculator.cpp │
│ ├── test_string_utils.cpp │
│ └── test_database.cpp │
│ │
│ Naming Convention: │
│ ───────────────────────────────────────────────── │
│ TEST(SuiteName, TestName) │
│ │ │ │ │
│ │ │ └─── What is being tested │
│ │ └───────────── Class or feature being tested │
│ └─────────────────────── TEST macro │
│ │
│ Examples: │
│ TEST(Calculator, AddsTwoPositiveNumbers) │
│ TEST(Calculator, ReturnsZeroForEmptyInput) │
│ TEST(StringUtils, TrimsWhitespace) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Parameterized Tests
Test the same logic with different inputs:
class MultiplicationTest : public ::testing::TestWithParam<std::tuple<int, int, int>> {
};
TEST_P(MultiplicationTest, Multiplies) {
auto [a, b, expected] = GetParam();
EXPECT_EQ(a * b, expected);
}
INSTANTIATE_TEST_SUITE_P(
MultiplicationTests,
MultiplicationTest,
::testing::Values(
std::make_tuple(2, 3, 6),
std::make_tuple(0, 5, 0),
std::make_tuple(-2, 3, -6),
std::make_tuple(-2, -3, 6)
)
);
Typed Tests
Test the same logic with different types:
template <typename T>
class ContainerTest : public ::testing::Test {
public:
using Container = T;
};
using ContainerTypes = ::testing::Types<std::vector<int>, std::list<int>, std::deque<int>>;
TYPED_TEST_SUITE(ContainerTest, ContainerTypes);
TYPED_TEST(ContainerTest, StartsEmpty) {
TypeParam container;
EXPECT_TRUE(container.empty());
}
TYPED_TEST(ContainerTest, SizeIncreases) {
TypeParam container;
container.push_back(1);
EXPECT_EQ(container.size(), 1);
}
Google Mock
Mock objects for testing dependencies:
#include <gmock/gmock.h>
// Interface
class Database {
public:
virtual ~Database() = default;
virtual bool connect() = 0;
virtual std::string query(const std::string& sql) = 0;
};
// Mock
class MockDatabase : public Database {
public:
MOCK_METHOD(bool, connect, (), (override));
MOCK_METHOD(std::string, query, (const std::string& sql), (override));
};
// Test using mock
TEST(UserServiceTest, LoadsUserFromDatabase) {
MockDatabase mockDb;
EXPECT_CALL(mockDb, connect())
.WillOnce(::testing::Return(true));
EXPECT_CALL(mockDb, query("SELECT * FROM users WHERE id = 1"))
.WillOnce(::testing::Return("{\"name\": \"John\"}"));
UserService service(&mockDb);
User user = service.loadUser(1);
EXPECT_EQ(user.name, "John");
}
Best Practices
┌─────────────────────────────────────────────────────────────────────────┐
│ TESTING BEST PRACTICES │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ✓ DO │
│ ───────────────────────────────────────────────── │
│ • Write tests first (TDD) or alongside code │
│ • Test one thing per test │
│ • Use descriptive test names │
│ • Test edge cases (empty, null, max values) │
│ • Keep tests fast (< 1 second each) │
│ • Make tests independent (no shared state) │
│ • Use fixtures for common setup │
│ • Test both success and failure paths │
│ │
│ ✗ DON'T │
│ ───────────────────────────────────────────────── │
│ • Test private methods directly │
│ • Make tests depend on each other │
│ • Use magic numbers without explanation │
│ • Test trivial getters/setters │
│ • Write flaky tests (pass/fail randomly) │
│ • Ignore failed tests │
│ │
│ Arrange-Act-Assert (AAA) Pattern: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ TEST(Calculator, AddsNumbers) { │ │
│ │ // Arrange - set up test data │ │
│ │ Calculator calc; │ │
│ │ int a = 5, b = 3; │ │
│ │ │ │
│ │ // Act - perform the action │ │
│ │ int result = calc.add(a, b); │ │
│ │ │ │
│ │ // Assert - verify the result │ │
│ │ EXPECT_EQ(result, 8); │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Running Tests
# Build and run all tests
cmake --build build
ctest --test-dir build
# Run with verbose output
ctest --test-dir build -V
# Run specific test
./build/tests --gtest_filter="CalculatorTest.*"
# Run tests matching pattern
./build/tests --gtest_filter="*Add*"
# List all tests
./build/tests --gtest_list_tests
# Run with shuffled order (find dependencies)
./build/tests --gtest_shuffle
# Repeat tests (find flaky tests)
./build/tests --gtest_repeat=10
# Output XML report
./build/tests --gtest_output=xml:report.xml
Summary
| Concept | Purpose |
|---|---|
TEST() | Define a simple test |
TEST_F() | Test using a fixture |
EXPECT_* | Non-fatal assertions |
ASSERT_* | Fatal assertions |
SetUp/TearDown | Test fixture lifecycle |
TEST_P | Parameterized tests |
TYPED_TEST | Type-parameterized tests |
MOCK_METHOD | Create mock methods |
Next Steps
- •Practice with
examples.cpp - •Complete the
exercises.cppchallenges - •Try writing tests for your own projects
- •Explore Google Mock for dependency injection