463. Fake (페이크) - 실제 동작하지만 프로덕션에는 적합하지 않은 축소판 (인메모리 DB 등)

핵심 인사이트 (3줄 요약)

  1. 본질: 페이크(Fake)는 테스트 더블(Test Double)의 5형제 중 가장 거대하고 진짜에 가까운 존재로, 대본(Stub)대로 앵무새처럼 대답하는 수준을 넘어 내부에 맵(HashMap) 같은 짭짤한 진짜 로직을 품고 있어 실제로 상태가 변하고 동작하는 완벽한 가짜 구현체다.
  2. 가치: 진짜 오라클(Oracle) DB나 진짜 신용카드 결제망을 띄워서 테스트하려면 수 분이 걸리고 과금이 발생하지만, Fake 객체(예: H2 인메모리 DB)를 꽂아두면 진짜 SQL 쿼리나 결제 로직이 돌아가는 것과 99% 똑같은 리얼리티를 유지하면서도 0.01초 만에 테스트를 끝내는 극한의 통합 테스트(Integration Test) 환경을 제공한다.
  3. 융합: '로컬 개발 환경(Local Env)'과 완벽하게 융합된다. 서버 개발자가 비행기 안에서 인터넷(와이파이)이 끊겼을 때도, 타 서버 API나 외부 DB와 똑같이 동작하는 Fake 서버/DB를 띄워놓으면 혼자서도 완벽하게 코딩과 테스트를 이어갈 수 있는 오프라인 아키텍처의 구원자다.

Ⅰ. 개요 및 필요성 (Context & Necessity)

  • 개념: Fake는 문자 그대로 '정교한 위조품'이다. 스텁(Stub)은 find("kim")을 부르면 무조건 "kim"을 뱉는 바보다. 하지만 Fake DB는 내부에 HashMap을 갖고 있어서, save("kim")을 하면 진짜로 해시맵에 저장하고, 나중에 find("kim")을 부르면 해시맵을 뒤져서 "kim"을 꺼내준다. 겉에서 보면 진짜 DB와 구분이 안 갈 정도로 똑똑하게 동작하지만, 전원을 끄면 메모리(HashMap)가 날아가 버리므로 실제 라이브(Production) 환경에서는 절대 쓸 수 없는 장난감이다.

  • 필요성: TDD를 한답시고 Mock과 Stub으로 외부 DB 통신을 싹 다 가짜 대본으로 막아버렸다(행위 검증). 그런데 오픈 당일, DB에 데이터를 넣는 INSERT 쿼리문 스펠링에 오타가 나서 시스템이 와르르 무너졌다. Mock은 내 로직만 검사할 뿐 진짜 DB 쿼리가 맞게 짜였는지는 봐주지 않기 때문이다. 그렇다고 매번 단위 테스트 1만 개를 돌릴 때마다 진짜 무거운 오라클 DB를 켰다 껐다 하면 테스트가 3시간이 걸려 아무도 안 돌리게 된다. **"진짜처럼 쿼리를 다 받아주면서도, 빛의 속도로 켜지고 꺼지는 깃털 같은 DB"**가 절실히 필요했고, 이것이 Fake의 절대적 존재 이유다.

  • 💡 비유: Fake는 영화 촬영장의 **'가짜 병원 세트장'**과 같습니다. 스텁(Stub)이 그냥 종이에 "병원"이라고 써 붙인 허수아비라면, Fake 병원은 진짜 침대, 링거, 가짜 피(내부 로직)가 완벽하게 갖춰져 있습니다. 배우가 누우면 진짜 병원처럼 완벽하게 연기(테스트)가 가능합니다. 하지만 진짜 의사나 약(Production 기능)은 없기 때문에 실제 환자를 눕히면 큰일 납니다. 촬영(테스트)용도로만 쓰는 완벽한 미니어처입니다.

  • 등장 배경 및 발전 과정:

    1. 진짜 DB에 대한 공포: 옛날엔 테스트 코드 안에 진짜 DB 연결 코드를 박았다. 팀원 5명이 동시에 테스트를 돌리면 DB 데이터가 뒤섞여 테스트가 터지는 환경 결함(Flaky Test)에 시달렸다.
    2. 가짜 저장소 수제작: 개발자들이 빡쳐서 interface Repository를 만들고, 진짜 DB 구현체 옆에 class FakeRepository implements Repository { Map map; } 이라는 가짜 메모리 클래스를 수작업으로 짜서 테스트에만 주입(DI)하기 시작했다.
    3. H2와 Testcontainers의 대중화 (현재): 손으로 맵(Map)을 짜는 것도 귀찮다. 아예 자바 진영에선 JVM 메모리 안에서만 0.1초 만에 떴다 사라지는 H2 Database (초거대 Fake DB)가 천하를 통일했고, 최근엔 Testcontainers(도커 기반 가짜 환경)로 진화했다.
  • 📢 섹션 요약 비유: Fake 객체는 군대의 **'서바이벌 총(페인트건)'**입니다. 훈련(테스트)할 때 빵야! 하고 입으로 소리 내는 것(Mock)은 너무 실전 감각이 떨어지고, 진짜 실탄(Real DB)을 쏘면 아군이 죽습니다. 진짜 총과 무게/격발 느낌이 100% 똑같으면서 맞으면 물감만 터지는 페인트건(Fake)이야말로 부상자 없이 실전 전투력(통합 테스트)을 끌어올리는 최고의 훈련 도구입니다.


Ⅱ. 아키텍처 및 핵심 원리 (Deep Dive)

1. Fake 객체의 내부 아키텍처 (수제작 HashMap 패턴)

Stub은 대본을 짜야 하지만, Fake는 진짜 로직이 있어서 대본이 필요 없다.

[ FakeRepository 구현 예시 ]

// 프로덕션 코드에서는 진짜 DB를 쓰는 JdbcUserRepository가 주입되지만,
// 테스트 환경에서는 아래의 '가짜 메모리 객체'를 주입(DI)한다.
public class FakeUserRepository implements UserRepository {
    // DB를 흉내 내는 핵심: 내부 메모리(HashMap) 상태 저장소
    private Map<String, User> dataStore = new HashMap<>(); 
    
    @Override
    public void save(User user) {
        dataStore.put(user.getId(), user); // 진짜로 맵에 저장함!
    }
    
    @Override
    public User find(String id) {
        return dataStore.get(id); // 진짜로 맵에서 찾아옴!
    }
}

[ Fake를 이용한 상태 검증 테스트 ]

UserRepository fakeDb = new FakeUserRepository();
UserService service = new UserService(fakeDb);

// 1. Act: 회원을 저장한다.
service.registerUser(new User("id-1", "kim"));

// 2. Assert: Fake DB에 진짜로 "kim"이 들어갔는지 까본다.
// (Stub처럼 대본을 짤 필요가 없이 진짜 DB처럼 똑같이 행동한다!)
assertEquals("kim", fakeDb.find("id-1").getName()); 

2. 엔터프라이즈급 거대 Fake (H2 Database)

개발자가 손으로 HashMap을 짜면 단순한 save, find는 되지만, SELECT * FROM user WHERE age > 20 JOIN... 같은 복잡한 SQL 쿼리는 처리를 못 한다. 그래서 스프링(Spring) 같은 엔터프라이즈 환경에서는, 스프링 부팅 시 메모리(RAM)의 한 켠에 진짜 SQL을 다 알아듣는 가짜 RDBMS 엔진인 H2 Database를 1초 만에 띄운다. 스프링 테스트 코드가 끝나는 순간 H2 엔진은 메모리에서 허공으로 증발해버리며, 데이터 찌꺼기를 단 1바이트도 남기지 않는 완벽한 샌드박스(Sandbox) Fake를 제공한다.

  • 📢 섹션 요약 비유: 수제작 HashMap Fake는 집에서 아빠가 박스로 만들어준 **'종이 싱크대'**입니다. 진짜 물은 안 나옵니다. 반면 H2 Database Fake는 모델하우스에 설치된 **'진짜 물이 콸콸 나오는 미니 싱크대'**입니다. 며칠 뒤 모델하우스가 철거되면 같이 부서지지만, 테스트하는 동안만큼은 진짜 주방과 100% 완벽하게 똑같은 위력을 발휘합니다.

Ⅲ. 융합 비교 및 다각도 분석

1. Fake (페이크) vs Stub (스텁) vs Mock (목) 삼국지 2편

세 가지 테스트 더블이 어떻게 다른지 로직의 유무로 판단해야 한다.

대역 배우내부 로직 존재 여부상태 변경 여부테스트 코드의 모습
Stub (스텁)로직 없음 (깡통 자판기)상태 없음. 내가 대본에 "100 줘"라고 써놔야 100 줌.when(db.get()).thenReturn(100) (대본 짜기 피곤함)
Mock (목)감시 로직만 있음 (CCTV)상태 없음. 행위만 카운팅.verify(db).save() (행위 감시하기 피곤함)
Fake (페이크)진짜 로직 있음 (HashMap 등)진짜 상태 변함. save하면 get할 때 진짜 나옴.대본(when)도 감시(verify)도 필요 없음. 그냥 쌩으로 돌림.

과목 융합 관점

  • 소프트웨어 공학 (통합 테스트, Integration Test): TDD에서 단위 테스트(Unit Test)는 Mock과 Stub으로 도배해서 클래스 하나만 잘라서 1만 번 돌린다(광속). 하지만 결국 각 클래스(퍼즐 조각)를 다 끼워 맞추고 톱니바퀴가 맞물리는지 보는 통합 테스트가 필요하다. 이때 통합 테스트에 진짜 오라클 DB를 물려버리면(End-to-End) 속도가 너무 느리고 데이터가 오염된다. 단위 테스트의 '속도'와 통합 테스트의 '리얼리티'라는 딜레마를 완벽하게 해결해 주는 황금 타협점이 바로 H2와 같은 초고속 Fake 객체다.

  • 아키텍처 (육각형 아키텍처, 헥사고날): 클린 아키텍처에서 비즈니스 로직(도메인)은 밖의 DB나 UI 기술에 의존하면 안 된다(의존성 역전). DB 포트(Interface)를 뚫어놨을 때, 프로덕션에서는 MySQL어댑터를 꽂고, 로컬 개발/테스트 환경에서는 Fake어댑터(HashMap)를 자유자재로 갈아 끼우는 플러그 앤 플레이(Plug & Play)가 Fake의 핵심 사상과 완벽하게 융합된다.

  • 📢 섹션 요약 비유: **스텁(Stub)**은 선생님이 아이에게 "이 수학 문제집 답은 3번이야(대본)"라고 정답지를 몰래 쥐여주는 것입니다. **페이크(Fake)**는 진짜 수학 선생님 대신 챗GPT(Fake 로직)에게 문제를 풀게 시키는 것입니다. 정답지 없이도, 내부에 계산 로직이 있어서 어떤 문제를 던지든 진짜 선생님처럼 정확한 답을 척척 뱉어내는 훌륭한 가짜 선생님입니다.


Ⅳ. 실무 적용 및 기술사적 판단

실무 시나리오

  1. 시나리오 — 더러운 Mockito 대본 지옥에 빠진 테스트 코드: 개발팀이 단위 테스트를 짜는데, 비즈니스 로직이 너무 복잡해서 DB를 세 번 찌르고 외부 저장소를 두 번 찌른다. 이 5번의 찌름을 다 Mock으로 대본(when().thenReturn())을 짜려니, 테스트 코드의 80%가 Mock 대본 세팅 코드로 도배되었다. 정작 진짜 비즈니스 로직 Assert는 단 2줄뿐이었다. 게다가 로직이 조금만 바뀌어도 대본 100줄을 매번 뜯어고쳐야 해서(Fragile Test) 테스트 코드를 저주하게 되었다.

    • 아키텍트의 해결책: 과도한 상태 주입(Over-stubbing)에 대한 처방으로 Fake 도입을 지시해야 한다. 아키텍트는 "상태 저장(CRUD)이 복잡하게 얽힌 리포지토리(DB) 계층은 Mockito 대본 짜는 걸 멈춰라. 대신 ListHashMap을 내장한 FakeRepository 클래스를 하나만 뚝딱 만들어라"라고 교정한다. Fake 하나만 주입해 두면 대본 100줄을 짤 필요가 없다. 그냥 진짜 코드처럼 save, find를 마구 갈겨도 Fake 안에서 메모리 맵이 완벽하게 돌아가므로, 테스트 코드가 10분의 1로 날씬해지고 압도적으로 직관성이 살아난다.
  2. 시나리오 — H2 (Fake DB)의 배신, 방언(Dialect) 호환성 실패: 통합 테스트 환경을 구축하며 H2 Database(Fake)를 올렸다. 테스트를 돌리니 빛의 속도로 100% 통과했다(초록불). 팀원들은 환호하며 라이브 서버(MySQL)에 배포했다. 그런데 라이브 서버 부팅 즉시 시스템이 박살 났다. MySQL에만 있는 ON DUPLICATE KEY UPDATE라는 특수 쿼리 방언을 썼는데, H2(Fake) 환경은 MySQL 방언을 흉내 내는 설정(Mode=MySQL)이 누락되어 가짜 세계에선 통과했고 진짜 세상에선 터진 것이다.

    • 아키텍트의 해결책: **Fake 객체와 진짜 객체 간의 환상 괴리(Illusion of Parity)**다. H2 같은 Fake DB가 아무리 똑똑해도 MySQL이나 Oracle의 100% 완벽한 짭이 될 수는 없다. 아키텍트는 두 가지 길을 택해야 한다. 1) 쿼리를 특정 벤더에 종속되지 않는 표준 ANSI SQL(또는 JPA/ORM)로만 강제하여 Fake의 괴리를 막거나, 2) 최근 대세로 떠오르는 Testcontainers (도커) 기술을 써서, 가짜 H2 대신 테스트가 도는 10초 동안만 '진짜 MySQL 깡통 컨테이너'를 허공에 띄웠다 죽이는 완벽한 운영 일치(Dev/Prod Parity) 아키텍처로 뼈대를 갈아타야 한다.

도입 체크리스트

  • 비즈니스적: 개발자의 오프라인(비행기/기차) 작업 환경이 보장되는가? 타 팀의 회원 인증 API 서버가 아직 안 만들어졌거나 권한이 막혀 있다. 이때 "타 팀 API 뚫릴 때까지 개발 스톱입니다"라고 노는 주니어들이 널렸다. 뛰어난 시니어는 FakeAuthServer라는 가짜 클래스를 하나 만들어 로컬 스프링에 띄워버린다. 인터넷이 끊기든 옆 팀 서버가 터지든, 자기 로컬(Local) 노트북 안에서 Fake를 통해 결제 로직 전체 사이클을 100% 팽팽하게 굴리며 비즈니스 리드타임을 수호한다.
  • 기술적: Fake 객체 스스로의 신뢰성(버그)은 어떻게 담보하는가? Fake 안에 HashMap으로 저장 로직을 짰는데, 주니어가 FakeRepository의 로직에 오타를 내서 버그를 심어놨다. 이러면 이 Fake를 쓰는 수천 개의 테스트 코드가 일제히 억울한 빨간 피(Fail)를 뿜게 된다. **"가짜 객체(Fake)의 로직이 너무 복잡해지면, 그 가짜 객체를 검증하는 테스트 코드를 또 짜야 하는 역설"**에 빠진다. Fake는 극한으로 단순(단순 CRUD 메모리 저장)하게 짜야 하며, 조금이라도 복잡해지면 즉시 버려야 한다.

안티패턴

  • "Fake 안에 비즈니스 예외 로직 구겨 넣기": Fake DB 안에 "만약 저장하려는 이름이 5글자가 넘으면 에러를 뱉어라"라는 비즈니스 로직(Validation)까지 덕지덕지 구현해 놓는 안티패턴. Fake는 무지성 창고여야 한다. 비즈니스 룰은 진짜 프로덕션 코드(Service)에 있어야지, 가짜 깡통(Fake) 안에 똑같이 짜놓으면 코드 중복(DRY 위반)이 발생하고 나중에 동기화가 안 되어 지옥이 열린다.

  • 📢 섹션 요약 비유: 복잡한 로직이 낀 Fake 객체는, 영화 세트장의 가짜 엘리베이터에 **'진짜 모터와 철제 케이블(복잡한 로직)'**을 달아버리는 미친 짓과 같습니다. 문만 열리고 닫히게 가짜로 만들면 되는데, 진짜 엘리베이터처럼 작동하게 만들면 돈(유지보수 비용)이 진짜 건물 짓는 것만큼 들어갑니다. Fake는 껍데기와 저장 공간만 유지해야 최고의 가성비가 나옵니다.


Ⅴ. 기대효과 및 결론

정량/정성 기대효과

구분무수히 많은 Mock 대본 작성 (AS-IS)Fake 객체 기반의 메모리 통합 테스팅 (TO-BE)개선 효과
정량10개의 DB 조작에 대해 100줄의 Mock 대본 작성FakeRepository 주입으로 대본 0줄, 바로 실행테스트 코드 유지보수 및 작성 시간 80% 단축
정량테스트용 외부 망(QA 서버) API 의존성 100%로컬 Fake 서버 띄우기로 네트워크 의존성 0%로컬 개발 환경에서의 테스트 속도 및 독립성 무한대 확보
정성로직 하나 바꾸면 대본이 틀렸다며 100개 테스트 터짐Fake 맵 안에서 알아서 돌아가므로 리팩토링 유연함리팩토링 저항감 최소화 및 깨지지 않는 테스트(Robust Test) 구축

미래 전망

  • Testcontainers(도커)에 의한 Fake의 종말: 과거 H2 같은 Fake 인메모리 DB가 시장을 지배했지만, 최근에는 테스트 코드를 돌리는 순간 백그라운드에서 진짜 MySQL이나 진짜 Redis를 1초 만에 도커(Docker) 컨테이너로 허공에 띄웠다가 테스트 끝나면 죽여버리는 Testcontainers가 천하를 통일하고 있다. "가짜(Fake)를 왜 써? 컨테이너로 띄웠다 버리는 1초짜리 진짜(Real)를 쓰면 운영 환경과 100% 완벽히 똑같은데!"라는 논리가 대세가 되어, 수제작 Fake 객체는 점차 멸종의 길을 걷고 있다.
  • 클라우드 네이티브 LocalStack의 부상: AWS 환경에서 개발할 때, 진짜 AWS S3나 SQS를 쓰려면 과금이 펑펑 터진다. 그래서 AWS 환경 전체를 100% 모방하여 내 로컬 노트북에 가짜 AWS 우주를 띄워주는 LocalStack이라는 거대한 Fake 인프라 소프트웨어가 전 세계 클라우드 개발자들의 필수 장난감으로 거듭나고 있다.

참고 표준

  • xUnit Test Patterns의 Fake 정의: 제라드 메스자로스가 "실제 동작하는 구현체지만 프로덕션 환경에 적합하지 않은 축소판(Shortcut) 로직을 가진 객체"로 정의한 5대 테스트 더블 중 하나.
  • H2 Database Engine: 자바 생태계에서 JUnit 테스트를 돌릴 때 가장 널리 쓰이는 관계형(SQL) Fake DB. 1MB도 안 되는 미친 가벼움으로 스프링 부트(Spring Boot)의 기본 심장으로 내장되어 있다.

페이크(Fake) 객체는 완벽주의에 빠진 소프트웨어 공학에 던지는 **'실용주의의 구원'**이다. 모든 것을 Mock 대본으로 치밀하게 통제하려는 극단적 TDD(런던파)는 결국 대본 유지보수의 지옥을 낳았다. 반대로 매번 무거운 진짜 DB(오라클)를 띄우려는 전통파(고전파)는 속도의 지옥에 갇혔다. 기술사는 이 양극단의 딜레마 사이에서, "로직은 살려두되 몸뚱어리만 가벼운 짭짤한 복제본(Fake)"을 수술대 위에 턱 얹어둠으로써, 빛의 속도와 리얼리티라는 양립 불가능해 보이는 두 마리 토끼를 한 번에 포획하는 유연하고도 우아한 마술을 부려야 한다.

  • 📢 섹션 요약 비유: Fake는 군인들의 **'가상 현실(VR) 시뮬레이터 룸'**입니다. Mock(입으로 빵야빵야 하기)처럼 유치하지도 않고, 진짜 이라크 전장(Real DB)에 가서 피를 흘리며 총알을 맞을 필요도 없습니다. 안전한 방 안에서 고글만 쓰면 진짜 적군이 튀어나오고(로직) 진짜 총기 반동(상태 변화)이 느껴지는, 돈이 한 푼도 안 들면서 실전 감각은 100%인 궁극의 전투 훈련장입니다.

📌 관련 개념 맵 (Knowledge Graph)

개념 명칭관계 및 시너지 설명
스텁 (Stub) & 목 (Mock)테스트 더블의 형제들. 스텁과 목은 "대본(when)"을 일일이 짜줘야 움직이는 멍청한 인형이라면, Fake는 뇌(HashMap 로직)가 들어있어 대본 없이 스스로 똑똑하게 일하는 인조인간이다. (이전 장 460, 462번)
인메모리 DB (In-memory DB)Fake 개념이 가장 위대하게 실물화된 소프트웨어. H2, SQLite Memory 모드 등, 하드디스크가 아닌 RAM 위에서만 1초 만에 성을 지었다 부수는 모래성 DB.
의존성 주입 (DI)내 서비스(Service) 배때지에 진짜 DB 칩을 뺄지, Fake DB 칩을 끼울지 외부에서 찰칵찰칵 갈아 끼울 수 있게 해주는 객체지향 5대 원칙(SOLID)의 엑스칼리버.
테스트컨테이너 (Testcontainers)Fake(가짜)의 치명적 약점인 "진짜 DB(MySQL)랑 쿼리 문법(방언)이 달라서 억울하게 터져요"를 막기 위해 아예 테스트할 때 진짜 DB를 도커로 띄워버리는 차세대 융합 테스팅 툴.
로컬스택 (LocalStack)S3, DynamoDB 등 아마존 클라우드(AWS)의 모든 서비스를 내 컴퓨터 로컬에 통째로 짭(Fake)으로 띄워주는 미친 가성비의 클라우드 모방 인프라.

👶 어린이를 위한 3줄 비유 설명

  1. 내가 '장난감 가게 사장님' 훈련을 하려고, 진짜 돈(Real)을 들고 진짜 손님을 받을 순 없잖아요? (너무 느리고 돈을 잃을 위험이 큼)
  2. 그렇다고 허공에 대고 "네~ 손님 100원 받았습니다~" 하고 입으로만 가짜 연기(Mock)를 하면 연습이 잘 안 돼요.
  3. 그래서 부루마불의 **'가짜 종이 돈'**과 **'미니어처 계산기'**를 가져와서 진짜 장사하듯이 똑같이 돈을 주고받으며 훈련했어요. 이렇게 진짜는 아니지만 진짜처럼 완벽하게 움직이는 장난감 세트장을 **'페이크(Fake)'**라고 부른답니다!