1. 테스트 더블(Test Double)이란?
영화 촬영 시 위험한 신을 대신하는 스턴트 더블(Stunt Double)에서 유래한 용어입니다. 소프트웨어 테스트 시, 의존성이 있는 실제 객체를 사용하기 어렵거나 제어하기 힘들 때 이를 대신하여 사용하는 모든 가짜 객체를 통칭합니다.
단위 테스트의 핵심은 고립(Isolation)입니다. 테스트 대상이 의존하는 객체(DB, 외부 API 등) 때문에 테스트가 깨지지 않도록 이들을 '더블'로 교체하는 것이죠.

| 종류 | 역할 (비유) | 상세 설명 | 테스트 시나리오 (예시) | 핵심 코드 스니펫 |
| Dummy | 마네킹 | 인스턴스는 전달되나 실제 사용되지 않음. 파라미터 자리를 채우기 위함. | 레스토랑이 주방장을 보유하고 있는지 여부만 확인하고 싶을 때 | public string Cook(string name) => throw new NotImplementedException(); |
| Fake | 모조품 | 실제 객체와 유사하게 동작하지만, 단순화된 로직(In-Memory 등)을 가짐. | DB 대신 Dictionary를 사용해 실제 요리 결과가 나오는지 확인하고 싶을 때 | return _recipes.GetValueOrDefault(dishName, "Unknown"); |
| Stub | 앵무새 | 미리 정해진 질문에 대해 정해진 답변만 하도록 프로그래밍됨. | 주방장이 "상한 음식"이라고 응답했을 때 환불 로직이 작동하는지 확인할 때 | stubChef.ConfigureResponse("SPOILED_FOOD"); |
| Mock | 스파이 | 호출 여부, 횟수, 순서 등 행위(Behavior)를 기록하고 검증함. | 특정 메뉴 주문 시 주방장이 정확히 1번 호출되었는지 검증하고 싶을 때 | mockChef.VerifyCooked("Steak", 1); |
| Spy | 흥신소 | 실제 객체를 감싸서 기능을 수행하되, 그 과정의 정보를 몰래 기록함. | 실제 주방장이 요리를 하는 것을 유지하면서 호출 여부만 기록하고 싶을 때 | _cookCallCount++; return _realChef.Cook(dishName); |
2. [Deep Dive] 비유로 이해하는 테스트더블
모든 예시는 아래의 인터페이스를 기반으로 합니다.
public interface IChef
{
string Cook(string dishName);
int GetTimesCalled(string dishName);
}
1) Dummy : 자리만 채우는 엑스트라
객체는 필요하지만 그 안의 메서드가 실행될 일은 없을 때 사용합니다.
시나리오: "레스토랑이 주방장을 고용하고 있는가?"만 확인하고 싶을 때.
public class DummyChef : IChef {
public string Cook(string name) => throw new NotImplementedException();
public int GetTimesCalled(string name) => 0;
}
// 테스트: 요리에는 관심 없고 주방장 존재 여부만 체크
var restaurant = new Restaurant(new DummyChef());
Assert.IsTrue(restaurant.HasChef());
2) Fake : 그럴듯하게 흉내 내는 배우
실제 로직(DB)은 너무 무거우니, 메모리 상에서 가볍게 돌아가도록 만든 모조품입니다.
시나리오: "주문을 넣으면 설정된 레시피대로 음식이 나오는가?"
public class FakeChef : IChef {
private Dictionary<string, string> _recipes = new() { { "Steak", "Well-done" } };
public string Cook(string name) => _recipes.GetValueOrDefault(name, "Unknown");
public int GetTimesCalled(string name) => 0;
}
3) Stub : 대본만 읽는 앵무새
상태를 강제하는 데 목적이 있습니다. "무조건 실패" 혹은 "무조건 성공"과 같은 시나리오를 만들 때 씁니다.
시나리오: "주방장이 '상한 음식'을 내놓았을 때 레스토랑이 환불해 주는가?"
public class StubChef : IChef {
public string Cook(string name) => "SPOILED_FOOD"; // 무조건 상한 음식 반환
public int GetTimesCalled(string name) => 0;
}
4) Mock : 행위 검증
가장 많이 사용되는 형태입니다. 상태(값)보다 행위(호출 횟수, 순서)를 검증하는 데 집중합니다.
시나리오: "스테이크를 주문하면 주방장이 정확히 1번 호출되는가?"
public class MockChef : IChef {
public int CallCount { get; private set; }
public string Cook(string name) {
if(name == "Steak") CallCount++;
return "Steak";
}
public int GetTimesCalled(string name) => CallCount;
}
// 검증: mockChef.CallCount 가 1인지 확인
5) Spy : 기록 및 가로채기
실제 객체를 사용하고 싶지만, 중간에 로그를 남기거나 호출을 가로채고 싶을 때 사용합니다.
public class SpyChef : IChef {
private readonly IChef _realChef;
public int TotalCalls { get; private set; }
public SpyChef(IChef real) => _realChef = real;
public string Cook(string name) {
TotalCalls++; // 기록
return _realChef.Cook(name); // 실제 주방장에게 위임
}
}
3. 실전: 게임 캐릭터 레벨업 시스템 테스트 만들기
실제 개발 현장에서 테스트 더블을 어떻게 믹스해서 쓰는지 [게임 캐릭터 레벨업] 로직으로 가볍게 살펴봅시다.
[상황] 몬스터를 잡아서 경험치(EXP)가 가득 차면, 레벨업을 하고 데이터베이스에 저장한 뒤 축하 효과음을 재생해야 합니다.
1.Stub (상태 강제): "경험치 2배 이벤트 아이템"이 적용된 특수한 상황을 테스트하고 싶나요? 아이템 매니저가 무조건 이벤트 적용 중이라고 답하게 만드는 Stub을 넣으세요.
2.Fake (로직 대체): 레벨 정보를 진짜 서버 DB에 저장하려면 네트워크 통신도 해야 하고 너무 느리죠? 메모리 상의 int 변수 하나에 레벨을 저장하는 Fake 저장소를 쓰면 테스트 속도가 비약적으로 빨라집니다.
3.Mock (행위 검증): 레벨은 한 번 올랐는데 축하 효과음이 10번이나 울리면 안 되겠죠? 사운드 출력 함수가 정확히 1번 호출되었는지 Mock으로 감시하고 검증하세요.
4. 무엇을 선택해야 할까?
- 생성자 자만 채우면 된다면? 👉 Dummy
- 성공/실패 상황을 만들고 싶다면? 👉 Stub
- DB나 외부 서버가 너무 무겁다면? 👉 Fake
- 함수 호출 횟수가 중요하다면? 👉 Mock
- 진짜 기능을 쓰면서 감시만 하겠다? 👉 Spy
테스트 더블에 대해서 몰랐을때는
테스트코드에 사용되는 모든 가짜객체 == Mock 이라고 생각했는데요.
보통 자주쓰이는 라이브러리들의 이름이 mock이다 보니 기본적인 개념이 이렇게 잡혀버린 것 같습니다.
다양한 가짜 객체들 == 테스트더블
mock == 테스트더블 중 하나의 종류에 불과
단위 테스트에서 가짜 객체를 잘 활용하는 것만으로도 테스트의 속도와 신뢰성이 비약적으로 향상됩니다.
프로젝트에 각각의 테스트더블을 분리해서 사용해 보세요!
'IT > Backend | All' 카테고리의 다른 글
| C#으로 게임을? MonoGame이 여전히 매력적인 이유 (feat. Stardew Valley) (0) | 2026.02.26 |
|---|---|
| DLL지옥 탈출기: 어셈블리 바인딩 실패, Fusion Log Viewer로 범인 찾기 (0) | 2026.02.25 |
| Apache Jmeter | 서버에서 jmeter 실행 (0) | 2025.11.24 |
| 🧪테스트주도개발(TDD) 하기 | Apache Jmeter를 이용한 부하테스트 (1) | 2025.11.19 |
| 비동기 처리의 핵심 | 스레드, 멀티스레드, 큐 (0) | 2025.11.14 |