C++ Unit Testing | "단위 테스트" 가이드

C++ Unit Testing | "단위 테스트" 가이드

이 글의 핵심

C++ Unit Testing에 대한 실전 가이드입니다. 개념부터 실무 활용까지 예제와 함께 상세히 설명합니다.

단위 테스트란?

개별 함수나 클래스를 독립적으로 테스트

// 테스트할 코드
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++ 시리즈 전체 보기
  • C++ Adapter Pattern 완벽 가이드 | 인터페이스 변환과 호환성
  • C++ ADL |
  • C++ Aggregate Initialization |