Google Test for C++: From Setup to TEST, Fixtures, and CI
이 글의 핵심
Complete C++ unit testing guide with Google Test: setup, assertions, fixtures, parameterized tests, death tests, TDD workflow, CI integration, and production patterns with real-world examples.
Introduction: “I refactored and something else broke”
Without tests, refactors hide regressions until production. Unit tests verify functions and classes against expected outputs so CI can catch breaks on every commit.
If calculate changes from a + b to a * b by mistake, EXPECT_EQ(calculate(2, 3), 5) fails immediately with a clear message.
Environment: Google Test via FetchContent, vcpkg (vcpkg install gtest), or Conan. g++/Clang C++14+; on Windows, MSVC + vcpkg is a common combo.
Link gtest_main so you do not write main; or link gtest and provide RUN_ALL_TESTS() yourself.
Table of contents
- Setup with CMake
- Basic assertions
- Test fixtures
- Parameterized tests
- Death tests
- Real-world examples
- TDD workflow
- Common errors
- Debugging tips
- Best practices
- CI integration
- Production patterns
1. Setup with CMake
Method 1: FetchContent (recommended)
cmake_minimum_required(VERSION 3.14)
project(MyProject)
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.12.1
)
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)
enable_testing()
add_executable(myapp_test
test_main.cpp
test_calculator.cpp
)
target_link_libraries(myapp_test PRIVATE
gtest_main
myapp_lib # Your library under test
)
include(GoogleTest)
gtest_discover_tests(myapp_test)
Method 2: vcpkg
vcpkg install gtest
find_package(GTest CONFIG REQUIRED)
add_executable(myapp_test test_main.cpp)
target_link_libraries(myapp_test PRIVATE
GTest::gtest_main
myapp_lib
)
Method 3: Conan
# conanfile.txt
[requires]
gtest/1.12.1
[generators]
CMakeDeps
CMakeToolchain
find_package(GTest REQUIRED)
target_link_libraries(myapp_test PRIVATE GTest::gtest_main)
2. Basic assertions
EXPECT vs ASSERT
#include <gtest/gtest.h>
TEST(CalculatorTest, Addition) {
// EXPECT: continues after failure
EXPECT_EQ(2 + 2, 4);
EXPECT_EQ(3 + 3, 6); // Still runs even if above fails
// ASSERT: stops test on failure
int* ptr = new int(42);
ASSERT_NE(ptr, nullptr); // Must pass before using ptr
EXPECT_EQ(*ptr, 42); // Only runs if ptr is valid
delete ptr;
}
Common assertions
// Boolean
EXPECT_TRUE(condition);
EXPECT_FALSE(condition);
// Comparison
EXPECT_EQ(a, b); // ==
EXPECT_NE(a, b); // !=
EXPECT_LT(a, b); // <
EXPECT_LE(a, b); // <=
EXPECT_GT(a, b); // >
EXPECT_GE(a, b); // >=
// Strings
EXPECT_STREQ("hello", str); // C-strings
EXPECT_STRNE("world", str);
std::string s = "test";
EXPECT_EQ("test", s); // std::string works with EQ
// Floating point
EXPECT_FLOAT_EQ(1.0f, result);
EXPECT_DOUBLE_EQ(1.0, result);
EXPECT_NEAR(1.0, result, 0.001); // Within tolerance
// Exceptions
EXPECT_THROW(func(), std::runtime_error);
EXPECT_NO_THROW(func());
EXPECT_ANY_THROW(func());
Custom messages
EXPECT_EQ(expected, actual) << "Failed with input: " << input;
3. Test fixtures (TEST_F)
Basic fixture
class DatabaseTest : public ::testing::Test {
protected:
Database* db;
// Runs before each test
void SetUp() override {
db = new Database("test.db");
db->connect();
}
// Runs after each test
void TearDown() override {
db->disconnect();
delete 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_EQ(db->get("key"), "");
}
Suite-level setup (shared across tests)
class ExpensiveResourceTest : public ::testing::Test {
protected:
static Database* shared_db;
// Runs once before all tests in suite
static void SetUpTestSuite() {
shared_db = new Database("shared.db");
shared_db->connect();
}
// Runs once after all tests in suite
static void TearDownTestSuite() {
shared_db->disconnect();
delete shared_db;
shared_db = nullptr;
}
};
Database* ExpensiveResourceTest::shared_db = nullptr;
TEST_F(ExpensiveResourceTest, Test1) {
EXPECT_NE(shared_db, nullptr);
}
TEST_F(ExpensiveResourceTest, Test2) {
EXPECT_NE(shared_db, nullptr);
}
4. Parameterized tests
Basic parameterized test
class SquareTest : public ::testing::TestWithParam<std::pair<int, int>> {};
TEST_P(SquareTest, CheckSquare) {
auto [input, expected] = GetParam();
EXPECT_EQ(input * input, expected);
}
INSTANTIATE_TEST_SUITE_P(
SquareValues,
SquareTest,
::testing::Values(
std::make_pair(0, 0),
std::make_pair(1, 1),
std::make_pair(2, 4),
std::make_pair(3, 9),
std::make_pair(-2, 4)
)
);
Multiple parameters with Combine
class StringTest : public ::testing::TestWithParam<std::tuple<std::string, char, int>> {};
TEST_P(StringTest, CountChar) {
auto [str, ch, expected] = GetParam();
int count = std::count(str.begin(), str.end(), ch);
EXPECT_EQ(count, expected);
}
INSTANTIATE_TEST_SUITE_P(
StringCases,
StringTest,
::testing::Combine(
::testing::Values("hello", "world", "test"),
::testing::Values('l', 'o', 't'),
::testing::Range(0, 3)
)
);
Range and ValuesIn
// Range: [0, 10) step 2
INSTANTIATE_TEST_SUITE_P(RangeTest, MyTest, ::testing::Range(0, 10, 2));
// ValuesIn: from container
std::vector<int> inputs = {1, 2, 3, 5, 8, 13};
INSTANTIATE_TEST_SUITE_P(FibTest, MyTest, ::testing::ValuesIn(inputs));
5. Death tests
Basic death test
void DivideByZero(int a, int b) {
assert(b != 0 && "Division by zero");
return a / b;
}
TEST(MathDeathTest, DivisionByZero) {
EXPECT_DEATH(DivideByZero(10, 0), "Division by zero");
}
TEST(MathDeathTest, AbortOnError) {
ASSERT_DEATH({
if (condition) {
std::abort();
}
}, ".*"); // Regex for expected stderr
}
Platform-specific death tests
TEST(DeathTest, CrossPlatform) {
#ifdef NDEBUG
EXPECT_DEATH_IF_SUPPORTED(func(), "error");
#else
EXPECT_DEATH(func(), "error");
#endif
}
6. Real-world examples
Example 1: String utility class
class StringUtils {
public:
static std::string trim(const std::string& str) {
size_t start = str.find_first_not_of(" \t\n\r");
if (start == std::string::npos) return "";
size_t end = str.find_last_not_of(" \t\n\r");
return str.substr(start, end - start + 1);
}
static std::vector<std::string> split(const std::string& str, char delim) {
std::vector<std::string> result;
std::stringstream ss(str);
std::string item;
while (std::getline(ss, item, delim)) {
result.push_back(item);
}
return result;
}
};
TEST(StringUtilsTest, TrimWhitespace) {
EXPECT_EQ(StringUtils::trim(" hello "), "hello");
EXPECT_EQ(StringUtils::trim("\t\ntest\r\n"), "test");
EXPECT_EQ(StringUtils::trim(""), "");
EXPECT_EQ(StringUtils::trim(" "), "");
}
TEST(StringUtilsTest, SplitString) {
auto result = StringUtils::split("a,b,c", ',');
ASSERT_EQ(result.size(), 3);
EXPECT_EQ(result[0], "a");
EXPECT_EQ(result[1], "b");
EXPECT_EQ(result[2], "c");
}
Example 2: HTTP client with mock
class HttpClient {
public:
virtual ~HttpClient() = default;
virtual std::string get(const std::string& url) = 0;
};
class RealHttpClient : public HttpClient {
public:
std::string get(const std::string& url) override {
// Actual HTTP implementation
return "real response";
}
};
class MockHttpClient : public HttpClient {
public:
std::string get(const std::string& url) override {
return "mock response";
}
};
class ApiService {
HttpClient* client;
public:
explicit ApiService(HttpClient* c) : client(c) {}
std::string fetchData(const std::string& endpoint) {
return client->get("https://api.example.com/" + endpoint);
}
};
TEST(ApiServiceTest, FetchData) {
MockHttpClient mock;
ApiService service(&mock);
std::string result = service.fetchData("users");
EXPECT_EQ(result, "mock response");
}
Example 3: Thread-safe queue
template<typename T>
class ThreadSafeQueue {
std::queue<T> queue;
mutable std::mutex mutex;
public:
void push(T value) {
std::lock_guard<std::mutex> lock(mutex);
queue.push(std::move(value));
}
bool try_pop(T& value) {
std::lock_guard<std::mutex> lock(mutex);
if (queue.empty()) return false;
value = std::move(queue.front());
queue.pop();
return true;
}
size_t size() const {
std::lock_guard<std::mutex> lock(mutex);
return queue.size();
}
};
TEST(ThreadSafeQueueTest, PushAndPop) {
ThreadSafeQueue<int> q;
q.push(1);
q.push(2);
int value;
ASSERT_TRUE(q.try_pop(value));
EXPECT_EQ(value, 1);
ASSERT_TRUE(q.try_pop(value));
EXPECT_EQ(value, 2);
EXPECT_FALSE(q.try_pop(value));
}
TEST(ThreadSafeQueueTest, Concurrent) {
ThreadSafeQueue<int> q;
const int N = 1000;
std::thread producer([&]() {
for (int i = 0; i < N; ++i) {
q.push(i);
}
});
std::thread consumer([&]() {
int count = 0;
int value;
while (count < N) {
if (q.try_pop(value)) {
++count;
}
}
});
producer.join();
consumer.join();
EXPECT_EQ(q.size(), 0);
}
7. TDD workflow
Red-Green-Refactor cycle
// 1. RED: Write failing test
TEST(CalculatorTest, Multiply) {
Calculator calc;
EXPECT_EQ(calc.multiply(2, 3), 6); // Fails: multiply not implemented
}
// 2. GREEN: Minimal implementation
class Calculator {
public:
int multiply(int a, int b) {
return a * b; // Simplest solution
}
};
// 3. REFACTOR: Improve without breaking test
class Calculator {
public:
int multiply(int a, int b) {
// Add validation, logging, etc.
if (a == 0 || b == 0) return 0;
return a * b;
}
};
Test-first development
// Step 1: Write test for new feature
TEST(UserServiceTest, CreateUser) {
UserService service;
User user = service.createUser("john", "[email protected]");
EXPECT_EQ(user.name, "john");
EXPECT_EQ(user.email, "[email protected]");
EXPECT_FALSE(user.id.empty());
}
// Step 2: Implement to pass test
class UserService {
public:
User createUser(const std::string& name, const std::string& email) {
User user;
user.id = generateId();
user.name = name;
user.email = email;
return user;
}
};
8. Common errors and fixes
Error 1: Undefined reference to InitGoogleTest
undefined reference to `testing::InitGoogleTest(int*, char**)'
Fix: Link gtest_main or provide main
target_link_libraries(myapp_test PRIVATE gtest_main)
Error 2: Multiple definitions of main
multiple definition of `main'
Fix: Don’t define main when using gtest_main
// Remove this if using gtest_main
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
Error 3: pthread error on Linux
undefined reference to `pthread_create'
Fix: Link Threads
find_package(Threads REQUIRED)
target_link_libraries(myapp_test PRIVATE gtest_main Threads::Threads)
Error 4: Death test fails in Release
Expected: DivideByZero(10, 0) dies
Actual: it didn't die
Fix: assert() is disabled in Release (NDEBUG)
// Use custom assertion that works in Release
#define MY_ASSERT(cond, msg) \
if (!(cond)) { \
std::cerr << msg << std::endl; \
std::abort(); \
}
Error 5: Flaky tests in CI
Test passed locally but fails randomly in CI
Fix: Remove nondeterminism
// BAD: depends on timing
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// GOOD: use synchronization
std::condition_variable cv;
std::mutex m;
std::unique_lock<std::mutex> lock(m);
cv.wait(lock, []{ return ready; });
9. Debugging tips
Tip 1: Run specific tests
# Run all tests in suite
./myapp_test --gtest_filter=CalculatorTest.*
# Run specific test
./myapp_test --gtest_filter=CalculatorTest.Addition
# Exclude tests
./myapp_test --gtest_filter=-*Slow*
Tip 2: Repeat tests
# Repeat 100 times to catch flaky tests
./myapp_test --gtest_repeat=100
# Break on first failure
./myapp_test --gtest_repeat=100 --gtest_break_on_failure
Tip 3: Verbose output
# Show all test names
./myapp_test --gtest_list_tests
# Colorized output
./myapp_test --gtest_color=yes
Tip 4: SCOPED_TRACE for loops
TEST(LoopTest, MultipleValues) {
std::vector<int> inputs = {1, 2, 3, 4, 5};
for (size_t i = 0; i < inputs.size(); ++i) {
SCOPED_TRACE("Testing input at index " + std::to_string(i));
EXPECT_GT(inputs[i], 0);
}
}
Tip 5: Debug with GDB
# Build with debug symbols
cmake -DCMAKE_BUILD_TYPE=Debug ..
# Run under GDB
gdb --args ./myapp_test --gtest_filter=MyTest.Specific
(gdb) break MyTest_Specific_Test::TestBody
(gdb) run
10. Best practices
1. One assertion per test (when possible)
// BAD: Multiple unrelated assertions
TEST(UserTest, Everything) {
User user("john");
EXPECT_EQ(user.getName(), "john");
EXPECT_TRUE(user.isValid());
EXPECT_EQ(user.getAge(), 0);
}
// GOOD: Separate tests
TEST(UserTest, GetName) {
User user("john");
EXPECT_EQ(user.getName(), "john");
}
TEST(UserTest, IsValid) {
User user("john");
EXPECT_TRUE(user.isValid());
}
2. Descriptive test names
// BAD
TEST(UserTest, Test1) { }
// GOOD
TEST(UserTest, GetName_WithValidInput_ReturnsName) { }
TEST(UserTest, IsValid_WithEmptyName_ReturnsFalse) { }
3. AAA pattern (Arrange-Act-Assert)
TEST(CalculatorTest, Add_TwoPositiveNumbers_ReturnsSum) {
// Arrange
Calculator calc;
int a = 2, b = 3;
// Act
int result = calc.add(a, b);
// Assert
EXPECT_EQ(result, 5);
}
4. Test edge cases
TEST(StringTest, EdgeCases) {
EXPECT_EQ(trim(""), ""); // Empty
EXPECT_EQ(trim(" "), ""); // Only whitespace
EXPECT_EQ(trim("a"), "a"); // Single char
EXPECT_EQ(split("", ',').size(), 1); // Empty split
}
5. Avoid test interdependence
// BAD: Tests depend on order
static int counter = 0;
TEST(BadTest, First) { counter = 1; }
TEST(BadTest, Second) { EXPECT_EQ(counter, 1); } // Fragile!
// GOOD: Each test is independent
TEST(GoodTest, First) {
int counter = 0;
counter = 1;
EXPECT_EQ(counter, 1);
}
11. CI integration
GitHub Actions
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install dependencies
run: sudo apt-get install -y cmake g++
- name: Build
run: |
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
- name: Run tests
run: |
cd build
ctest --output-on-failure
- name: Generate coverage
run: |
cmake -B build -DCMAKE_BUILD_TYPE=Debug -DENABLE_COVERAGE=ON
cmake --build build
cd build
ctest
lcov --capture --directory . --output-file coverage.info
lcov --remove coverage.info '/usr/*' --output-file coverage.info
lcov --list coverage.info
CTest integration
enable_testing()
add_executable(myapp_test test_main.cpp)
target_link_libraries(myapp_test PRIVATE gtest_main myapp_lib)
include(GoogleTest)
gtest_discover_tests(myapp_test)
# Run all tests
ctest
# Verbose output
ctest -V
# Run specific test
ctest -R CalculatorTest
# Parallel execution
ctest -j4
12. Production patterns
Pattern 1: Separate test directory
project/
├── CMakeLists.txt
├── src/
│ ├── calculator.h
│ └── calculator.cpp
└── test/
├── CMakeLists.txt
└── test_calculator.cpp
Pattern 2: Test utilities
// test/test_utils.h
namespace test_utils {
inline std::string readFile(const std::string& path) {
std::ifstream file(path);
return std::string((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
}
inline void createTempFile(const std::string& path, const std::string& content) {
std::ofstream file(path);
file << content;
}
}
Pattern 3: Custom matchers
testing::AssertionResult IsEven(int n) {
if (n % 2 == 0)
return testing::AssertionSuccess();
else
return testing::AssertionFailure() << n << " is odd";
}
TEST(NumberTest, EvenNumbers) {
EXPECT_TRUE(IsEven(2));
EXPECT_TRUE(IsEven(4));
}
Summary
- Setup: FetchContent, vcpkg, or Conan
- Assertions: EXPECT_* (continue) vs ASSERT_* (stop)
- Fixtures: TEST_F for setup/teardown
- Parameterized: TEST_P for data-driven tests
- Death tests: EXPECT_DEATH for abort/exit verification
- TDD: Red-Green-Refactor cycle
- CI: ctest integration with GitHub Actions
- Best practices: One assertion per test, descriptive names, AAA pattern
Next: Google Mock (#18-2)
Previous: Package managers (#17-2)
Keywords
Google Test, gtest, C++ unit testing, TDD, CTest, test fixtures, parameterized tests
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. Complete C++ unit testing guide with Google Test: FetchContent and vcpkg setup, TEST and TEST_F, EXPECT vs ASSERT, param… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ Google Mock | ‘DB 없이 테스트하고 싶어요’ Mock 객체로 의존성 분리
- C++ 로깅·Assertion | 프로덕션 간헐적 크래시, 로그 없이 재현 불가일 때
- C++ 테스트 전략 완벽 가이드 | 단위·통합·E2E·모킹·프로덕션 패턴 [#55-7]
이 글에서 다루는 키워드 (관련 검색어)
C++, testing, Google Test, gtest, unit-testing, TDD, CTest 등으로 검색하시면 이 글이 도움이 됩니다.