테스트 더블(Mock, Stub, Spy, Fake)은 단위테스트의 핵심 도구입니다. 각 개념의 차이와 실전 활용법, 오해와 진실, 실무 적용 사례까지 초보자도 쉽게 이해할 수 있도록 안내합니다. 예제와 실전 Q&A, 실수 방지 전략, 커리어 팁, 최신 트렌드 및 레퍼런스까지 모두 담았습니다.

테스트 더블이란?

테스트 더블(Test Double)은 실제 객체를 대신해 테스트에서 사용되는 “가짜 객체”를 의미합니다. 단위테스트, 통합테스트 등에서 외부 의존성(데이터베이스, 네트워크, 외부 API 등)이나 복잡한 객체를 대체해 테스트를 더 쉽고 빠르게 만들어줍니다. 테스트 더블을 활용하면 다양한 시나리오(성공, 실패, 예외, 장애 등)를 자유롭게 시뮬레이션할 수 있고, 테스트의 신뢰성과 반복 가능성을 크게 높일 수 있습니다.

1. 테스트 더블이 필요한 이유
  • 외부 시스템, DB, 네트워크 등 느리거나 불안정한 의존성을 대체해 테스트를 빠르고 안정적으로 실행할 수 있습니다.
  • 예외 상황, 장애 상황 등 다양한 시나리오를 쉽게 재현할 수 있습니다.
  • 테스트 속도 향상 및 반복 가능성 보장: 외부 환경 변화에 영향을 받지 않고 항상 동일한 결과를 얻을 수 있습니다.
  • 실제 환경에 영향 없이 안전하게 테스트 가능: 실서비스 데이터나 외부 시스템에 영향을 주지 않고 테스트할 수 있습니다.
  • 테스트 케이스의 독립성/재현성 확보: 각 테스트가 독립적으로 동작하며, 언제든 동일한 결과를 재현할 수 있습니다.
  • 복잡한 비즈니스 로직, 상태 변화, 에러 처리 등 다양한 경계 케이스를 쉽게 검증할 수 있습니다.
  • CI/CD 파이프라인 등 자동화 환경에서 신속하고 안정적인 테스트를 구현할 수 있습니다.
2. 주요 테스트 더블의 종류와 차이

| 유형 | 목적/주요 특징 | 예시 | |——-|—————-|——| | Stub | 미리 정해진 값을 반환하는 가짜 객체. 외부 시스템 결과를 고정해 테스트할 때 사용 | 외부 API가 항상 “성공”을 반환하도록 설정, DB 조회 결과 고정 등 | | Mock | 호출/행위(메서드 호출 여부, 횟수 등)를 검증할 수 있는 더블. 행위 기반 검증에 사용 | 결제 API 호출 횟수/파라미터 검증, 이메일 발송 여부 등 | | Spy | 실제 객체를 감싸 일부만 가짜로 대체. 실제 동작과 호출 기록을 모두 확인 가능 | 일부 메서드는 실제 동작, 일부만 가짜로 대체해 복합 시나리오 테스트 | | Fake | 실제 동작과 유사하지만 단순화된 구현(인메모리 DB, 임시 파일 등) | 실제 DB 대신 인메모리 DB, 임시 파일 시스템 등 |

이 네 가지는 목적, 구현 방식, 사용 시점이 다르므로 상황에 따라 적절히 선택해야 합니다.

3. 각 더블의 실전 예시 및 코드

Stub 예시 (Kotlin/Mockk)

class ExternalApi {
    fun fetchData(): String = "실제 데이터"
}

@Test
fun stubExample() {
    val api = mockk<ExternalApi>()
    every { api.fetchData() } returns "테스트 데이터"
    assert(api.fetchData() == "테스트 데이터")
}
  • 외부 API가 항상 “테스트 데이터”를 반환하도록 Stub 처리

Mock 예시 (Kotlin/Mockk)

class PaymentService {
    fun pay(amount: Int) {}
}

@Test
fun mockExample() {
    val paymentService = mockk<PaymentService>()
    every { paymentService.pay(any()) } just Runs
    paymentService.pay(100)
    verify(exactly = 1) { paymentService.pay(100) }
}
  • 결제 API가 실제로 몇 번 호출됐는지, 어떤 파라미터로 호출됐는지 검증

Spy 예시 (Kotlin/Mockk)

class UserService {
    fun createUser(name: String): Boolean {
        // 실제 사용자 생성 로직
        return true
    }
}

@Test
fun spyExample() {
    val userService = spyk(UserService())
    every { userService.createUser("테스트") } returns false
    assert(!userService.createUser("테스트"))
    assert(userService.createUser("실제") == true)
}
  • “테스트”라는 입력만 가짜로 처리, 나머지는 실제 동작

Fake 예시 (Kotlin)

class FakeRepository : UserRepository {
    private val users = mutableListOf<String>()
    override fun save(user: String) { users.add(user) }
    override fun findAll(): List<String> = users
}

// 실제 DB 대신 인메모리로 동작하는 Fake
4. 실무에서 자주 쓰는 라이브러리/도구
  • Java/Kotlin: Mockito, Mockk, EasyMock, JMockit 등
  • Python: unittest.mock, pytest-mock 등
  • JavaScript/TypeScript: Jest, Sinon.js 등
  • 기타: Spock, NSubstitute, Moq 등
언어/플랫폼 대표 라이브러리 특징
Java Mockito, EasyMock 풍부한 기능, 대규모 프로젝트에 적합
Kotlin Mockk Kotlin 친화적, DSL 스타일, 코루틴 지원
Python unittest.mock 표준 내장, patch 등 다양한 기능
JS/TS Jest, Sinon.js 함수/모듈 단위 Mock, 풍부한 생태계
.NET Moq, NSubstitute C# 친화적, 강력한 행위 검증
5. 실전 활용 전략 및 시나리오
  • 복잡한 외부 시스템 의존성(Fake/Stub)으로 대체해 테스트 속도와 안정성 확보
  • Mock/Spy로 행위 기반 검증(몇 번 호출, 어떤 파라미터 등) 적극 활용
  • 테스트 코드의 가독성, 유지보수성을 위해 더블 생성/설정은 공통 함수/클래스로 분리
  • 예외 상황, 장애, 경계 케이스 등 다양한 시나리오를 더블로 쉽게 재현
  • CI/CD 파이프라인에서 외부 시스템 장애/지연을 Stub/Fake로 시뮬레이션
  • 테스트 데이터/상태를 매번 초기화해 테스트 독립성 보장
  • 테스트 더블 남용은 실제 환경과 괴리, 과도한 행위 검증, 테스트 유지보수 난이도 증가로 이어질 수 있으니 주의
6. 오해와 진실
  • “Mock과 Stub은 같다?” → Stub은 값 반환, Mock은 행위 검증이 주목적
  • “Fake는 실무에서 잘 안 쓴다?” → 인메모리 DB, 임시 파일 등 Fake는 실제로 현업에서 매우 자주 활용
  • “더블은 테스트 신뢰도를 떨어뜨린다?” → 오히려 테스트의 독립성과 신뢰도를 높여줌. 단, 실제 환경과 차이가 크면 과신은 금물
  • “테스트 더블은 단위테스트에서만 쓴다?” → 통합테스트, E2E 테스트 등 다양한 레벨에서 활용 가능
  • “Mock은 항상 필요한가?” → 단순 반환 검증이면 Stub, 행위 검증이 필요할 때만 Mock/Spy 활용
7. 실전 Q&A/실수 방지
  • Q. 테스트 더블을 남용하면 안 되는 경우는?
    • 실제 비즈니스 로직/복잡한 상태 변화는 실제 객체로 검증 필요
    • 외부 시스템과의 통합, 실제 환경과의 차이로 인한 문제 발생 가능
  • Q. Mock과 Spy의 차이와 선택 기준은?
    • Mock은 모든 동작이 가짜, Spy는 일부만 가짜(나머지는 실제 동작)
    • 복합 시나리오, 부분적 행위 검증이 필요하면 Spy 활용
  • Q. 테스트 더블로 외부 API 장애/지연을 어떻게 재현?
    • Stub/Fake에서 예외/지연 반환 설정, 네트워크 장애 시뮬레이션 등
  • Q. 더블 남용 시 문제점은?
    • 실제 환경과 괴리, 과도한 행위 검증, 테스트 유지보수 난이도 증가
  • 실수 방지:
    • 더블의 목적/종류를 명확히 구분하고, 꼭 필요한 곳에만 사용
    • 테스트 데이터/상태를 매번 초기화
    • 공통 더블/설정은 재사용 가능한 구조로 관리
    • 테스트 코드에 과도한 복잡성, 불필요한 행위 검증이 들어가지 않도록 주의
8. 커리어/실무 적용 팁
  • 신입/경력 면접에서 “Mock, Stub, Spy, Fake 차이와 활용 경험”은 단골 질문
  • 실제 코드/테스트 예시, 오픈소스 PR, 블로그 포스팅 등으로 경험을 어필
  • 테스트 더블 활용 능력은 단위테스트, TDD, CI/CD 등 실무 역량과 직결
  • 실무에서 자주 쓰는 더블 패턴(예: 인메모리 DB, 임시 파일, Mock API 서버 등) 경험 강조
  • 코드 리뷰, 문서화, 커뮤니티 활동 등으로 테스트 더블 활용 노하우를 공유하면 커리어에 큰 도움
9. 최신 트렌드/확장 사례
  • AI 기반 테스트 더블 자동 생성/추천(예: Copilot, ChatGPT 활용)
  • 컨테이너/클라우드 환경(인메모리 DB, Mock API 서버 등)에서의 활용 증가
  • 테스트 더블과 계약 기반 테스트(Contract Test, Pact 등) 결합
  • 마이크로서비스, 이벤트 기반 아키텍처에서의 더블 활용법
  • 테스트 더블 자동화 도구, 시나리오 기반 테스트 생성, 데이터 시뮬레이션 등 최신 기술 동향
10. 용어 설명
  • 테스트 더블(Test Double): 테스트에서 실제 객체를 대신하는 모든 가짜 객체의 총칭
  • Stub/Mock/Spy/Fake: 더블의 세부 유형. 목적/특성에 따라 구분
  • 행위 검증(Behavior Verification): 메서드 호출 여부, 횟수, 파라미터 등 검증
  • 상태 검증(State Verification): 반환값, 객체 상태 등 검증
  • 인메모리 DB(In-memory DB): 실제 DB 대신 메모리에서 동작하는 임시 DB. Fake 구현에 자주 활용
  • 계약 기반 테스트(Contract Test): 서비스 간 API 계약을 검증하는 테스트. 더블과 결합해 신뢰성 강화
  • 테스트 시나리오(Test Scenario): 다양한 상황(성공, 실패, 예외 등)을 시뮬레이션하는 테스트 케이스

결론 및 도움말

테스트 더블(Mock, Stub, Spy, Fake)은 단위테스트, 통합테스트에서 필수 도구입니다. 각 개념의 차이와 실전 활용법, 오해와 진실, 실수 방지 전략까지 익히면 테스트 자동화와 품질 향상에 큰 도움이 됩니다. 공식 문서, 오픈소스 예제, 커뮤니티 자료를 적극 활용해보세요!


실전 비교 표: Mock, Stub, Spy, Fake 한눈에 보기

| 구분 | Stub | Mock | Spy | Fake | |——|——|——|—–|——| | 목적 | 반환값 고정 | 행위(호출) 검증 | 일부만 가짜, 나머지 실제 동작 | 단순화된 실제 구현 | | 검증 방식 | 상태(값) | 행위(호출/파라미터) | 상태 + 행위 | 실제 동작 유사 | | 예시 | 외부 API 성공/실패 고정 | 결제 API 호출 횟수 검증 | 일부 메서드만 가짜 | 인메모리 DB, 임시 파일 | | 사용 시점 | 단순 시나리오 | 복잡한 행위 검증 | 복합 시나리오 | 테스트 환경 격리 | | 대표 도구 | Mockk, Mockito | Mockk, Mockito | Mockk, Mockito | TestContainers, H2DB |

현업 적용 실전 시나리오

  • Stub: 외부 API 장애/지연/예외를 Stub으로 시뮬레이션하여, 장애 대응 로직(재시도, 타임아웃, 폴백 등) 테스트
  • Mock: 결제 시스템, 이메일 발송 등 외부 호출이 반드시 일어나야 하는 상황에서, 호출 횟수/파라미터/순서 등 행위 검증
  • Spy: 실제 객체의 일부 메서드만 가짜로 대체하여, 복합 로직(예: 일부만 장애, 일부만 성공) 테스트
  • Fake: 실서비스 DB 대신 인메모리 DB, 임시 파일 시스템 등으로 테스트 데이터 격리 및 빠른 테스트

실수 방지 체크리스트

  • Stub/Mock/Spy/Fake 목적과 차이를 명확히 이해하고, 상황에 맞게 사용한다
  • Fake 사용 시 실제 환경과의 차이(트랜잭션, 인덱스, 제약조건 등)를 반드시 문서화한다
  • Mock 남용 시 실제 통합 환경에서 문제 발생 가능성을 항상 염두에 둔다
  • 테스트 데이터/상태를 테스트마다 초기화해 독립성을 보장한다
  • 테스트 코드에 불필요한 복잡성, 과도한 행위 검증이 들어가지 않도록 한다

커리어 성장 Q&A

  • Q. “테스트 더블 경험을 어떻게 어필하면 좋을까요?”
    • 실제 코드/테스트 예시, 오픈소스 PR, 블로그 포스팅, 코드 리뷰 경험 등으로 구체적으로 설명
  • Q. “Mock과 Stub, Spy, Fake의 차이를 한 문장으로 설명해보세요.”
    • Stub: 값 반환, Mock: 행위 검증, Spy: 일부만 가짜, Fake: 단순화된 실제 구현
  • Q. “실제 현업에서 가장 자주 쓰는 더블은?”
    • 외부 API Stub, 인메모리 DB Fake, 행위 검증 Mock이 가장 빈번
  • Q. “테스트 더블이 커리어에 미치는 영향은?”
    • 테스트 자동화, CI/CD, 코드 품질 향상, 협업/리뷰 역량 등 실무 핵심 역량으로 직결

미래 전망과 트렌드

  • AI 기반 테스트 더블 자동 생성(예: Copilot, ChatGPT 활용) 확산
  • 컨테이너/클라우드 환경에서 TestContainers, Mock API 서버 등 도구 활용 증가
  • 계약 기반 테스트(Contract Test)와의 결합으로 마이크로서비스 신뢰성 강화
  • 오픈소스/커뮤니티 중심의 실전 예제, 자동화 플러그인, 시나리오 기반 테스트 생성 도구 발전

실전 코드 시나리오: 장애 상황 시뮬레이션

Kotlin/Mockk 예시 - 외부 API 장애 재현

class ExternalApi {
    fun fetchData(): String = "실제 데이터"
}

@Test
fun stubFailureExample() {
    val api = mockk<ExternalApi>()
    every { api.fetchData() } throws RuntimeException("API 장애")
    try {
        api.fetchData()
        fail("예외가 발생해야 합니다")
    } catch (e: RuntimeException) {
        assert(e.message == "API 장애")
    }
}

TDD/CI/CD 통합 실전 팁

  • TDD: Mock/Stub으로 외부 의존성 제거, 빠른 피드백 루프
  • CI: Fake/Mock API 서버로 외부 시스템 없이도 자동화 테스트 가능
  • CD: 장애/지연/예외 시나리오를 Stub/Fake로 시뮬레이션해, 배포 전 품질 검증 강화

커뮤니티/오픈소스 활용법

  • 공식 문서, 오픈소스 예제, 스택오버플로우, GitHub Discussions 등에서 실전 경험과 문제 해결 팁을 적극적으로 습득
  • Mockk, Mockito, TestContainers, MockServer 등 각 도구의 공식 샘플 코드와 이슈 트래커 활용

현업에서 마주치는 실전 사례와 교훈

  • 실패 사례:
    • 실제 DB와 Fake DB의 동작 차이(트랜잭션, 인덱스, 제약조건 등)로 인해, 테스트는 성공하지만 실제 서비스에서는 장애가 발생하는 경우가 있음. Fake를 쓸 때는 실제 환경과의 차이를 반드시 문서화하고, 주요 경계 케이스는 실제 환경에서 한 번 더 검증해야 함.
    • Mock을 남용해 모든 의존성을 가짜로 만들면, 테스트는 통과하지만 실제 통합 환경에서는 오류가 발생. Mock은 꼭 필요한 부분(외부 API, 네트워크 등)에만 제한적으로 사용하고, 핵심 로직은 실제 객체로 검증하는 것이 바람직함.
  • 성공 사례:
    • 외부 결제 시스템 장애를 Stub으로 시뮬레이션해, 장애 시 자동 롤백/알림 로직을 미리 검증해 실제 장애 상황에서 빠르게 대응한 경험.
    • CI/CD 파이프라인에서 Mock API 서버를 활용해, 외부 시스템이 없어도 자동화 테스트가 항상 성공적으로 동작하도록 구성.

다양한 언어/플랫폼별 테스트 더블 코드 예시

Java (Mockito)

import static org.mockito.Mockito.*;

List mockedList = mock(List.class);
when(mockedList.get(0)).thenReturn("first");
assert "first".equals(mockedList.get(0));
verify(mockedList).get(0);

Python (unittest.mock)

from unittest.mock import Mock
api = Mock()
api.fetch_data.return_value = "테스트 데이터"
assert api.fetch_data() == "테스트 데이터"
api.fetch_data.assert_called_once()

JavaScript (Jest)

const fetchData = jest.fn();
fetchData.mockReturnValue("테스트 데이터");
expect(fetchData()).toBe("테스트 데이터");
expect(fetchData).toHaveBeenCalledTimes(1);

테스트 더블과 TDD/BDD, CI/CD 파이프라인 연계

  • TDD(Test Driven Development)에서는 Mock/Stub을 적극 활용해, 외부 의존성 없이 빠르게 테스트-개발-리팩터링 사이클을 반복할 수 있습니다.
  • BDD(Behavior Driven Development)에서는 행위 기반 검증(Mock, Spy)을 통해, 사용자의 기대 행동을 명확히 테스트로 표현할 수 있습니다.
  • CI/CD 자동화에서는 외부 시스템 장애, 네트워크 지연, 예외 상황 등을 Stub/Fake로 시뮬레이션해, 언제나 신뢰할 수 있는 테스트 환경을 구축할 수 있습니다.

자동화 도구, 오픈소스, 커뮤니티 활용법

  • Mock API 서버(Prism, WireMock, MockServer 등)로 실제 API 없이도 프론트-백엔드 동시 개발 및 테스트 가능
  • 오픈소스 예제, 공식 문서, 커뮤니티 Q&A(스택오버플로우, GitHub Discussions 등)를 적극 활용해, 다양한 실전 노하우 습득
  • 테스트 더블 자동 생성 도구/플러그인(Copilot, Mockaroo, TestContainers 등) 활용으로 생산성 극대화

실무에서 자주 묻는 질문(FAQ)

  • Q. “Stub과 Mock을 언제 구분해서 써야 하나요?”
    • 반환값만 검증하면 Stub, 행위(호출 횟수/파라미터 등) 검증이 필요하면 Mock
  • Q. “Fake DB로 모든 테스트를 대체해도 되나요?”
    • 주요 비즈니스 로직, 트랜잭션, 인덱스 등은 실제 DB 환경에서 한 번 더 검증 필요
  • Q. “테스트 더블이 많아질수록 테스트가 더 안전해지나요?”
    • 오히려 과도한 더블은 실제 환경과의 괴리를 키울 수 있으니, 꼭 필요한 곳에만 적용
  • Q. “테스트 더블의 한계는?”
    • 실제 환경과 100% 동일하게 시뮬레이션할 수 없으므로, 중요한 경계 케이스는 실제 환경에서 반드시 추가 검증

각 더블별 한계와 대안, 미래 전망

  • Stub/Mock은 단순 시나리오에는 유용하지만, 복잡한 상태나 비동기/이벤트 기반 시스템에서는 Fake, Contract Test, E2E 테스트와 병행 필요
  • AI 기반 테스트 더블 자동 생성, 데이터 시뮬레이션, 계약 기반 테스트(Contract Test)와의 결합 등 최신 트렌드에 주목
  • 클라우드/마이크로서비스 환경에서는 Mock API Gateway, Service Mesh, TestContainers 등과 연계해 더블 전략을 고도화하는 것이 실무 트렌드
  • 커리어 성장에는 테스트 더블 활용 경험, 코드 리뷰, 문서화, 커뮤니티 활동이 큰 도움이 됨

레퍼런스