TDD 테스트주도개발/TDD for C++

[googletest문법6]TEST_F()확실하게 이해하자!

CodeMasterSong 2025. 4. 9. 01:01
반응형

GoogleTest TEST_F() - 예제로 배우는 C++ 테스트 픽스처

 

앞서 예제를 통해 TEST()와 TEST_F()의 차이점까지 이해가 되었다면 간단하지만 조금 더 실무적인 예제를 통해 TEST_F()의 사용법을 이해해 볼 수 있습니다. 또한 결과를 보기위해 다양한 방법으로 컴파일 할 수 있지만 좀 더 사용 용도가 많은 구조를 만들어 보기위해 프로젝트 내에 src, test가 분리되며 각 폴더에 CMakeLists.txt를 사용해 보겠습니다.


1. 프로젝트 구조

이번 예제는 CMake 기반으로 구성하고, src와 test 폴더를 분리하여 실제 프로젝트와 유사한 구조로 만들어봅니다.

MyProject/ 
├── src 
│ ├── CMakeLists.txt 
│ ├── UserManager.h 
│ └── UserManager.cpp 
├──test 
│  ├── CMakeLists.txt 
│  ├── test_user_manager.cpp 
│  └── test_main.cpp
└── CMakeLists.txt
  • CMakeLists.txt: 프로젝트 루트에 위치하며, 하위 디렉터리(src, test)를 포함해 전체 빌드를 관리합니다.
  • src/: 실제 기능을 구현하는 C++ 소스 코드가 들어갑니다.
  • test/: 구글테스트 기반으로 테스트 코드를 작성하는 폴더입니다.

1.1 최상위 CMakeLists.txt 예시

cmake_minimum_required(VERSION 3.10)
project(MyProject)

# C++17 이상 사용 예시
set(CMAKE_CXX_STANDARD 17)

# 구글테스트를 FetchContent로 자동 다운로드하는 예시
# (직접 설치된 googletest를 쓰는 경우, 아래 로직 대신 find_package(GTest ...) 등을 사용)
include(FetchContent)
FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG        release-1.12.1  # 원하는 버전
)
FetchContent_MakeAvailable(googletest)

# src 디렉터리 포함
add_subdirectory(src)

# test 디렉터리 포함
enable_testing()
add_subdirectory(test)

참고:
Cygwin 환경에서 이미 googletest 패키지를 설치했다면 FetchContent로 굳이 내려받지 않고, find_package(GTest REQUIRED) 식으로 구성해도 됩니다.

 

1.2 src/CMakeLists.txt 예시

# src/CMakeLists.txt
add_library(MyLibrary
    UserManager.cpp
)

target_include_directories(MyLibrary
    PUBLIC
    ${CMAKE_CURRENT_SOURCE_DIR}
)
  • MyLibrary라는 이름의 라이브러리를 생성합니다.
  • UserManager.cpp를 컴파일하고, 필요한 헤더 검색 경로를 설정합니다.

1.3 test/CMakeLists.txt 예시

# test/CMakeLists.txt
add_executable(MyTests
    test_user_manager.cpp
)

# 테스트 코드가 MyLibrary를 링크하도록 설정
target_link_libraries(MyTests
    PRIVATE
    MyLibrary
    gtest
    gtest_main
    pthread  # (Cygwin에서 필요할 수 있음)
)

# CTest 사용 가능하도록
add_test(NAME MyTests COMMAND MyTests)
 
  • MyTests라는 실행 파일에 test_user_manager.cpp를 포함합니다.
  • MyLibrary와 gtest, gtest_main 라이브러리를 링크합니다.
  • add_test를 통해 CTest에서 테스트를 수행할 수 있도록 등록합니다.

2. UserManager 클래스 

UserManager는 여러 사용자 정보를 관리하는 간단한 클래스로, 아래 기능을 제공합니다.

  • 사용자 추가: AddUser()
  • 사용자 삭제: RemoveUser()
  • 사용자 조회: GetUser()
  • 총 사용자 수 확인: GetUserCount()

여기서는 단순하게 사용자 정보를 std::map<int, std::string>에 저장하며, int를 사용자 ID, std::string을 사용자 이름으로 가정합니다.

2.1 src/UserManager.h

#ifndef USER_MANAGER_H
#define USER_MANAGER_H

#include <map>
#include <string>
#include <stdexcept>

class UserManager {
public:
    // 사용자 추가 (중복 ID는 예외 처리)
    void AddUser(int userId, const std::string& userName);

    // 사용자 삭제 (없는 ID 삭제 시 false 반환)
    bool RemoveUser(int userId);

    // 사용자 조회 (없는 ID 조회 시 예외)
    std::string GetUser(int userId) const;

    // 전체 사용자 수
    size_t GetUserCount() const;

private:
    // ID -> 사용자 이름 매핑
    std::map<int, std::string> users_;
};

#endif // USER_MANAGER_H

2.2 UserManager.cpp

#include "UserManager.h"

void UserManager::AddUser(int userId, const std::string& userName) {
    // 이미 존재하는 ID라면 예외
    if (users_.find(userId) != users_.end()) {
        throw std::runtime_error("User ID already exists.");
    }
    users_[userId] = userName;
}

bool UserManager::RemoveUser(int userId) {
    auto it = users_.find(userId);
    if (it == users_.end()) {
        return false; // 존재하지 않음
    }
    users_.erase(it);
    return true;
}

std::string UserManager::GetUser(int userId) const {
    auto it = users_.find(userId);
    if (it == users_.end()) {
        throw std::runtime_error("User not found.");
    }
    return it->second;
}

size_t UserManager::GetUserCount() const {
    return users_.size();
}

3. 테스트 코드 - TEST_F() 예제

이번에는 TEST_F()를 사용해 테스트 픽스처(Test Fixture)를 구성해봅니다. 아래는 test_user_manager.cpp 파일의 예시입니다.

3.1 test_user_manager.cpp

#include <gtest/gtest.h>
#include "../src/UserManager.h"

// 테스트 픽스처 클래스
class UserManagerTest : public ::testing::Test {
protected:
    void SetUp() override {
        // 각 테스트마다 새로운 UserManager 객체를 생성
        userManager = new UserManager();

        // 테스트 공통으로 사용할 샘플 데이터 추가
        userManager->AddUser(1, "Alice");
        userManager->AddUser(2, "Bob");
    }

    void TearDown() override {
        // 동적 할당 해제
        delete userManager;
    }

    UserManager* userManager;
};

// TEST_F()를 이용해 공통 픽스처인 userManager를 테스트에 활용

TEST_F(UserManagerTest, AddUser_Success) {
    // 이미 SetUp()에서 1, 2 ID를 추가해둠
    EXPECT_NO_THROW(userManager->AddUser(3, "Charlie"));

    // 총 3명이 되었는지 확인
    EXPECT_EQ(userManager->GetUserCount(), 3u);
}

TEST_F(UserManagerTest, AddUser_DuplicateID) {
    // 중복 ID 추가 시도 (이미 1번 ID가 존재)
    EXPECT_THROW(userManager->AddUser(1, "AnotherAlice"), std::runtime_error);
}

TEST_F(UserManagerTest, RemoveUser_Success) {
    // 2번 ID 제거
    bool removed = userManager->RemoveUser(2);
    EXPECT_TRUE(removed);

    // 제거 후 사용자 수 확인
    EXPECT_EQ(userManager->GetUserCount(), 1u);
}

TEST_F(UserManagerTest, RemoveUser_NotExisting) {
    // 999번 ID는 존재하지 않음
    bool removed = userManager->RemoveUser(999);
    EXPECT_FALSE(removed);
}

TEST_F(UserManagerTest, GetUser_Success) {
    // 1번 ID("Alice")가 맞는지 확인
    EXPECT_EQ(userManager->GetUser(1), "Alice");
}

TEST_F(UserManagerTest, GetUser_NotFound) {
    // 존재하지 않는 999번 ID 조회 시 예외
    EXPECT_THROW(userManager->GetUser(999), std::runtime_error);
}
 
 

3.2 테스트 픽스처 사용 요령

  • UserManagerTest는 ::testing::Test를 상속받으며, SetUp()에서 userManager 객체를 생성하고 샘플 사용자 2명을 미리 등록합니다.
  • 각 테스트(TEST_F)는 이 픽스처를 공유하되, 매번 독립된 환경(새로운 UserManager)으로 실행됩니다.
  • 테스트가 끝나면 TearDown()에서 해제하므로, 테스트끼리 서로 영향을 주지 않습니다.

4. 빌드 및 실행

빌드 및 실행을 위해 Cygwin 터미널에서 프로젝트 루트(MyProject/)로 이동 후 다음 명령어를 순서대로 실행합니다.

  • 성공적으로 빌드되면 MyTests라는 실행 파일이 생성됩니다.
$ mkdir build && cd build 
$ cmake .. 
$ make

 

$ ./MyTests
[==========] Running 6 tests from 1 test suite. 
[----------] Global test environment set-up. 
[----------] 6 tests from UserManagerTest 
[      RUN ] UserManagerTest.AddUser_Success 
[       OK ] UserManagerTest.AddUser_Success (0 ms) 
[      RUN ] UserManagerTest.AddUser_DuplicateID 
[       OK ] UserManagerTest.AddUser_DuplicateID (0 ms) 
[      RUN ] UserManagerTest.RemoveUser_Success 
[       OK ] UserManagerTest.RemoveUser_Success (0 ms) 
[      RUN ] UserManagerTest.RemoveUser_NotExisting 
[       OK ] UserManagerTest.RemoveUser_NotExisting (0 ms) 
[      RUN ] UserManagerTest.GetUser_Success 
[       OK ] UserManagerTest.GetUser_Success (0 ms) 
[      RUN ] UserManagerTest.GetUser_NotFound 
[       OK ] UserManagerTest.GetUser_NotFound (0 ms) 
[----------] 6 tests from UserManagerTest (0 ms total) 
[----------] Global test environment tear-down 
[==========] 6 tests from 1 test suite ran. (0 ms total) 
[ PASSED ] 6 tests.

5. 마무리 및 확장 아이디어

  • TEST_F()를 사용하면 여러 테스트에서 공통으로 사용할 객체나 리소스를 쉽게 관리할 수 있어 실무에서도 매우 자주 활용됩니다.
  • 이번 예제에서는 사용자 ID/이름 관리의 간단한 시나리오를 다뤘지만, 실제 업무 환경에서는 데이터베이스 연결, 서버 소켓, 파일 입출력, 복잡한 계산 로직 등을 픽스처로 설정해두고 재사용하는 경우가 많습니다.
  • 픽스처가 복잡해지면 SetUpTestSuite()나 TearDownTestSuite()(클래스 스코프에서 한 번만 실행) 같은 함수도 고려해볼 수 있습니다.
  • 추가로, TEST_P()와 INSTANTIATE_TEST_SUITE_P() 등을 사용하면 매개변수화된 테스트도 손쉽게 구현할 수 있으니, 필요하다면 구글테스트의 공식 문서를 참고해보세요.

이상으로, C++ 프로젝트에서 Google Test의 TEST_F()를 사용해 테스트 픽스처를 구성하는 실무 예시를 살펴보았습니다. 테스트 코드가 점점 늘어가더라도, 픽스처를 잘 관리하면 테스트의 가독성과 유지 보수성이 크게 향상됩니다. 필요에 따라 더 복잡한 시나리오나 환경 설정을 추가하면서 자신의 프로젝트에 맞춰 확장해보길 바랍니다.

반응형