C++: Unit Testing
Last updated: 2026-08-26
In lesson 49, we learned about performance optimization.
Now, we'll learn about unit testing — the key skill for ensuring code quality.
Writing code without testing is like cooking without tasting.
1. Unit Testing Overview
(1) 1.1 What Is Unit Testing?
Unit testing involves testing the smallest testable units (functions, classes).
Advantages:
- Catch bugs early
- Safety net for refactoring
- Documents code behavior
(2) 1.2 Choosing a Test Framework
| Framework | Description |
|---|---|
| Google Test | Made by Google, most popular |
| Catch2 | Single header file, simple to use |
| Boost.Test | Part of the Boost libraries |
| Doctest | Similar to Catch2, faster |
Recommendation: Google Test (full-featured) or Catch2 (simple)
2. Getting Started with Google Test
(1) 2.1 Installing Google Test
Linux:
sudo apt-get install libgtest-dev
Windows: Use vcpkg or compile from source.
(2) 2.2 Your First Test
Example: Testing an add function with Google Test (Difficulty ⭐)
▶ Example 2: Code example (Difficulty ⭐)
#include <gtest/gtest.h>
int add(int a, int b) {
return a + b;
}
TEST(AddTest, Positive) {
EXPECT_EQ(add(2, 3), 5);
}
TEST(AddTest, Negative) {
EXPECT_EQ(add(-2, -3), -5);
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Output:
[==========] Running 2 tests from 1 test suite.
[ PASSED ] 2 tests.
Run result:
[==========] Running 2 tests from 1 test suite.
[ PASSED ] 2 tests.
3. Assertion Macros
(1) 3.1 ASSERT vs EXPECT
| Macro | Behavior on Failure |
|---|---|
ASSERT_* |
Stops the current test |
EXPECT_* |
Continues running |
Recommendation: Use EXPECT_* (you can see more failures)
(2) 3.2 Common Assertions
| Assertion | Description |
|---|---|
EXPECT_EQ(a, b) |
a == b |
EXPECT_NE(a, b) |
a != b |
EXPECT_LT(a, b) |
a < b |
EXPECT_GT(a, b) |
a > b |
EXPECT_TRUE(cond) |
cond is true |
EXPECT_FALSE(cond) |
cond is false |
EXPECT_THROW(statement, exception_type) |
Expects an exception to be thrown |
4. Test Fixtures
(1) 4.1 Why Do We Need Fixtures?
Problem: Multiple tests need the same initialization code.
Solution: Test fixtures (inherit from testing::Test)
(2) 4.2 Example: Testing a queue with fixtures (Difficulty ⭐⭐)
#include <gtest/gtest.h>
#include <queue>
class QueueTest : public testing::Test {
protected:
void SetUp() override {
q.push(1);
q.push(2);
q.push(3);
}
void TearDown() override {
// Cleanup (if needed)
}
std::queue<int> q;
};
TEST_F(QueueTest, Size) {
EXPECT_EQ(q.size(), 3);
}
TEST_F(QueueTest, Front) {
EXPECT_EQ(q.front(), 1);
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Output:
(Program output)
5. Test-Driven Development (TDD)
(1) 5.1 What Is TDD?
TDD (Test-Driven Development) means writing tests first, then writing code.
Process:
- Write a test (it fails)
- Write the code (it passes)
- Refactor
(2) 5.2 Example: TDD for a Stack (Difficulty ⭐⭐⭐)
// 1. Write the test first
TEST(StackTest, PushAndPop) {
Stackint s;
s.push(42);
EXPECT_EQ(s.pop(), 42);
}
// 2. Then write the code
template<typename T>
class Stack {
std::vectorT data;
public:
void push(const T& x) { data.push_back(x); }
T pop() { T x = data.back(); data.pop_back(); return x; }
};
Output:
(Program output)
6. Practice: Testing a Student Class
▶ Example 1: Complete test (Difficulty ⭐⭐⭐)
#include <gtest/gtest.h>
#include <string>
class Student {
std::string name;
int age;
public:
Student(const std::string& name, int age) : name(name), age(age) {}
std::string getName() const { return name; }
int getAge() const { return age; }
};
TEST(StudentTest, Constructor) {
Student s("Zhang San", 20);
EXPECT_EQ(s.getName(), "Zhang San");
EXPECT_EQ(s.getAge(), 20);
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Output:
(Program output)
▶ Example 3: Simple unit test (Difficulty ⭐)
#include <iostream>
#include <cassert>
// Functions being tested
int multiply(int a, int b) {
return a * b;
}
bool isPositive(int n) {
return n > 0;
}
int main() {
// Test multiply
assert(multiply(2, 3) == 6);
assert(multiply(-2, 3) == -6);
assert(multiply(0, 5) == 0);
std::cout << "multiply Test passed" << std::endl;
// Test isPositive
assert(isPositive(5) == true);
assert(isPositive(-1) == false);
assert(isPositive(0) == false);
std::cout << "isPositive Test passed" << std::endl;
std::cout << "All tests passed!" << std::endl;
return 0;
}
Output:
multiply Test passed
isPositive Test passed
All tests passed!
❓ FAQ
Q: Can unit testing find all bugs? A: No. Unit tests can only find known types of errors. You also need integration testing and system testing.
📖 Summary
| Key Point | Summary |
|---|---|
| Unit testing | Test the smallest units |
| Google Test | Most popular C++ test framework |
| Assertion macros | EXPECT_* and ASSERT_* |
| Test fixtures | Share initialization code |
| TDD | Write tests first, then code |
📝 Exercises
-
Basic (Difficulty ⭐): Write an
add(a, b)function and create 3 test cases with Google Test: adding positive numbers, adding negative numbers, adding zeros. -
Intermediate (Difficulty ⭐⭐): Write an
isPalindromefunction that checks if a string is a palindrome. Write at least 5 test cases covering normal, boundary, and special inputs. -
Challenge (Difficulty ⭐⭐⭐): Write complete unit tests for a "Bank Account" class (with deposit/withdraw/getBalance methods). Test normal deposits/withdrawals, overdraft rejection, and thread safety (if applicable).
- Unit testing verifies the correctness of functions or modules
- Test frameworks: Google Test / Catch2 / doctest
- Test case structure: Arrange → Act → Assert
- Test-driven development (TDD): write tests first, then code
- Code coverage measures how thorough your tests are
Next lesson: Practice — Contact Management System (#51)