Cute Light Pink Flying Butterfly 테스트 더블 |"가짜"라도 다 같은 가짜가 아니다. 5가지 테스트더블, 상황에 맞게 선택하기 :: 놀면서 돈벌기
본문 바로가기
IT/Backend | All

테스트 더블 |"가짜"라도 다 같은 가짜가 아니다. 5가지 테스트더블, 상황에 맞게 선택하기

by esclife_ 2026. 2. 25.
반응형

 

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 == 테스트더블 중 하나의 종류에 불과

 

단위 테스트에서 가짜 객체를 잘 활용하는 것만으로도 테스트의 속도와 신뢰성이 비약적으로 향상됩니다. 

프로젝트에 각각의 테스트더블을 분리해서 사용해 보세요!

반응형