테스트 더블(Mock, Stub, Spy, Fake) 활용법 - 실전과 오해까지
테스트 더블(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 등과 연계해 더블 전략을 고도화하는 것이 실무 트렌드
- 커리어 성장에는 테스트 더블 활용 경험, 코드 리뷰, 문서화, 커뮤니티 활동이 큰 도움이 됨