C++ 단위 테스트 완벽 가이드 | Google Test로 배우는 TDD (실전 예제)
이 글의 핵심
C++ Unit Testing: 단위 테스트 가이드. 단위 테스트란?·Google Test 기본.
단위 테스트란?
개별 함수나 클래스를 독립적으로 테스트
// 테스트할 코드
int add(int a, int b) {
return a + b;
}
// 테스트 코드
TEST(AddTest, BasicTest) {
EXPECT_EQ(add(2, 3), 5);
EXPECT_EQ(add(-1, 1), 0);
}
Google Test 기본
#include <gtest/gtest.h>
// 테스트 작성
TEST(TestSuiteName, TestName) {
EXPECT_EQ(1 + 1, 2);
ASSERT_TRUE(true);
}
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
# 컴파일
g++ test.cpp -lgtest -lgtest_main -pthread
# 실행
./a.out
실전 예시
예시 1: 기본 테스트
#include <gtest/gtest.h>
class Calculator {
public:
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) {
if (b == 0) throw std::invalid_argument("0으로 나눌 수 없음");
return a / b;
}
};
TEST(CalculatorTest, AddTest) {
Calculator calc;
EXPECT_EQ(calc.add(2, 3), 5);
EXPECT_EQ(calc.add(-1, 1), 0);
}
TEST(CalculatorTest, DivideTest) {
Calculator calc;
EXPECT_EQ(calc.divide(10, 2), 5);
EXPECT_THROW(calc.divide(10, 0), std::invalid_argument);
}
예시 2: Fixture 사용
class VectorTest : public ::testing::Test {
protected:
void SetUp() override {
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
}
void TearDown() override {
vec.clear();
}
std::vector<int> vec;
};
TEST_F(VectorTest, SizeTest) {
EXPECT_EQ(vec.size(), 3);
}
TEST_F(VectorTest, ElementTest) {
EXPECT_EQ(vec[0], 1);
EXPECT_EQ(vec[1], 2);
}
예시 3: 매개변수화 테스트
class AddTest : public ::testing::TestWithParam<std::tuple<int, int, int>> {};
TEST_P(AddTest, ParameterizedTest) {
auto [a, b, expected] = GetParam();
EXPECT_EQ(add(a, b), expected);
}
INSTANTIATE_TEST_SUITE_P(
AddTests,
AddTest,
::testing::Values(
std::make_tuple(1, 2, 3),
std::make_tuple(0, 0, 0),
std::make_tuple(-1, 1, 0)
)
);
예시 4: Mock 객체
#include <gmock/gmock.h>
class Database {
public:
virtual ~Database() = default;
virtual bool connect() = 0;
virtual std::string query(const std::string& sql) = 0;
};
class MockDatabase : public Database {
public:
MOCK_METHOD(bool, connect, (), (override));
MOCK_METHOD(std::string, query, (const std::string&), (override));
};
TEST(ServiceTest, MockTest) {
MockDatabase mockDb;
EXPECT_CALL(mockDb, connect())
.WillOnce(::testing::Return(true));
EXPECT_CALL(mockDb, query("SELECT *"))
.WillOnce(::testing::Return("result"));
EXPECT_TRUE(mockDb.connect());
EXPECT_EQ(mockDb.query("SELECT *"), "result");
}
Catch2 사용
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
TEST_CASE("Calculator tests", "[calculator]") {
Calculator calc;
SECTION("Addition") {
REQUIRE(calc.add(2, 3) == 5);
}
SECTION("Division") {
REQUIRE(calc.divide(10, 2) == 5);
REQUIRE_THROWS(calc.divide(10, 0));
}
}
자주 발생하는 문제
문제 1: 테스트 의존성
// ❌ 테스트 간 의존성
TEST(BadTest, Test1) {
globalVar = 10;
}
TEST(BadTest, Test2) {
EXPECT_EQ(globalVar, 10); // Test1에 의존
}
// ✅ 독립적 테스트
TEST(GoodTest, Test1) {
int var = 10;
EXPECT_EQ(var, 10);
}
TEST(GoodTest, Test2) {
int var = 10;
EXPECT_EQ(var, 10);
}
문제 2: 너무 큰 테스트
// ❌ 여러 것을 한 번에 테스트
TEST(BadTest, EverythingTest) {
// 10개 함수 테스트
}
// ✅ 하나씩 테스트
TEST(GoodTest, AddTest) {
// add 함수만
}
TEST(GoodTest, SubtractTest) {
// subtract 함수만
}
문제 3: 외부 의존성
// ❌ 파일 시스템 의존
TEST(BadTest, FileTest) {
std::ifstream file("/tmp/test.txt");
// 파일 없으면 실패
}
// ✅ Mock 사용
TEST(GoodTest, FileTest) {
MockFileSystem fs;
// Mock으로 제어
}
문제 4: 느린 테스트
// ❌ 느린 작업
TEST(BadTest, SlowTest) {
std::this_thread::sleep_for(std::chrono::seconds(10));
}
// ✅ 빠른 테스트
TEST(GoodTest, FastTest) {
// 즉시 완료
}
TDD (Test-Driven Development)
// 1. 실패하는 테스트 작성
TEST(StackTest, PushTest) {
Stack<int> stack;
stack.push(10);
EXPECT_EQ(stack.top(), 10);
}
// 2. 최소 구현
template<typename T>
class Stack {
std::vector<T> data;
public:
void push(const T& value) {
data.push_back(value);
}
T top() const {
return data.back();
}
};
// 3. 리팩토링
FAQ
Q1: 단위 테스트는 언제?
A:
- 새 기능 추가
- 버그 수정
- 리팩토링
Q2: Google Test vs Catch2?
A:
- Google Test: 기능 많음
- Catch2: 간단, 헤더 온리
Q3: 테스트 커버리지 목표는?
A:
- 최소 70%
- 핵심 로직 100%
Q4: Mock은 언제?
A:
- 외부 의존성
- 네트워크, 파일
- 느린 작업
Q5: TDD 장점은?
A:
- 설계 개선
- 버그 조기 발견
- 문서화
Q6: 단위 테스트 학습 리소스는?
A:
- Google Test 문서
- “Test Driven Development”
- Catch2 문서
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ Google Test | gtest 설치부터 TEST·EXPECT_EQ
- C++ Benchmarking | “벤치마킹” 가이드
- C++ 코드 커버리지 완벽 가이드 | gcov, lcov, Codecov 실전 활용
내부 동작과 핵심 메커니즘
이 글의 주제는 「C++ 단위 테스트 완벽 가이드 | Google Test로 배우는 TDD (실전 예제)」입니다. 여기서는 앞선 설명을 구현·런타임 관점에서 한 번 더 압축합니다. 시스템·런타임 경계(스케줄링, I/O, 메모리, 동시성)를 기준으로 생각하면, “입력이 어디서 검증되고, 핵심 연산이 어디서 일어나며, 부작용(I/O·네트워크·디스크)이 어디서 터지는가”가 한눈에 드러납니다.
처리 파이프라인(개념도)
flowchart TD A[입력·요청·이벤트] --> B[파싱·검증·디코딩] B --> C[핵심 연산·상태 전이] C --> D[부작용: I/O·네트워크·동시성] D --> E[결과·관측·저장]
알고리즘·프로토콜 관점에서의 체크포인트
- 불변 조건(Invariant): 각 단계가 만족해야 하는 조건(예: 버퍼 경계, 프로토콜 상태, 트랜잭션 격리)을 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
- 결정성: 동일 입력에 동일 출력이 보장되는 순수한 층과, 시간·네트워크에 의해 달라질 수 있는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
- 경계 비용: 직렬화/역직렬화, 문자 인코딩, syscall 횟수, 락 경합처럼 “한 번의 호출이 아니라 누적되는 비용”을 의심 목록에 넣습니다.
프로덕션 운영 패턴
실서비스에서는 기능 구현과 함께 관측·배포·보안·비용이 동시에 요구됩니다. 아래는 팀에서 자주 쓰는 최소 체크리스트입니다.
| 영역 | 운영 관점에서의 질문 |
|---|---|
| 관측성 | 요청 단위 상관 ID, 에러율/지연 분위수, 주요 의존성 타임아웃이 보이는가 |
| 안전성 | 입력 검증·권한·비밀 관리가 코드 경로마다 일관적인가 |
| 신뢰성 | 재시도는 멱등한 연산에만 적용되는가, 서킷 브레이커·백오프가 있는가 |
| 성능 | 캐시 계층·배치 크기·풀링·백프레셔가 데이터 규모에 맞는가 |
| 배포 | 롤백 룬북, 카나리, 마이그레이션 호환성이 문서화되어 있는가 |
운영 환경에서는 “개발자 PC에서는 재현되지 않던 문제”가 시간·부하·데이터 크기 때문에 드러납니다. 따라서 스테이징의 데이터 양·네트워크 지연을 가능한 한 현실에 가깝게 맞추는 것이 중요합니다.
문제 해결(Troubleshooting)
| 증상 | 가능 원인 | 조치 |
|---|---|---|
| 간헐적 실패 | 레이스 컨디션, 타임아웃, 외부 의존성 불안정 | 최소 재현 스크립트 작성, 분산 트레이스·로그 상관관계 확인 |
| 성능 저하 | N+1 쿼리, 동기 I/O, 잠금 경합, 과도한 직렬화 | 프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거 |
| 메모리 증가 | 캐시 무제한, 클로저/이벤트 구독 누수, 대용량 객체의 불필요한 복사 | 상한·TTL·스냅샷 비교(힙 덤프/트레이스) |
| 빌드·배포만 실패 | 환경 변수·권한·플랫폼 차이 | CI 로그와 로컬 diff, 컨테이너/런타임 버전 핀(pin) |
권장 디버깅 순서: (1) 최소 재현 만들기 (2) 최근 변경 범위 좁히기 (3) 의존성·환경 변수 차이 확인 (4) 관측 데이터로 가설 검증 (5) 수정 후 회귀·부하 테스트.
관련 글
이 글에서 다루는 키워드 (관련 검색어)
C++, unit-testing, testing, gtest, TDD 등으로 검색하시면 이 글이 도움이 됩니다.