460. Stub (스텁) - 호출 시 준비된 답변만 반환 (상태 검증용)

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

  1. 본질: 스텁(Stub)은 단위 테스트 시 내 로직이 외부 객체(DB, API)에 의존하여 값을 받아와야 할 때, 외부 연동 없이 내가 미리 훈련시켜 놓은 대사("이 함수를 부르면 무조건 100원을 줘라")를 무지성으로 리턴해 주는 가짜 자판기 로봇이다.
  2. 가치: 진짜 카카오페이 서버가 다운되어 있거나 아직 개발 전이더라도, 1초 만에 "성공" 응답을 뱉는 스텁을 꽂아 내 시스템을 100% 격리(Isolation) 상태에서 독립적으로 테스트하게 해주며, 특히 "통신 장애(500 에러)가 났을 때 내 로직이 잘 버티는지" 엣지 케이스를 강제로 주입하는 데 최적화되어 있다.
  3. 융합: 객체가 "무슨 값을 주느냐"에 집중하는 **상태 검증(State Verification)**의 핵심 도구이며, 외부 호출이 난무하는 마이크로서비스(MSA) 아키텍처 환경에서 다른 팀 서버의 멱살을 잡지 않고 내 팀의 CI 파이프라인을 빛의 속도로 굴러가게 만드는 필수 모듈이다.

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

  • 개념: Stub은 '나무의 밑동, 토막'을 뜻한다. 잘라내서 무언가를 임시로 받쳐놓는다는 뉘앙스다. 테스트를 돌리려면 getUserAge()라는 남의 함수를 불러야 하는데, 그 함수가 완성 안 됐다면? 내가 그냥 getUserAge()라는 껍데기를 만들고, 무조건 25라는 숫자를 뱉어내도록(Return) 조작해 놓은 나무토막(가짜 객체)을 만들어 쑤셔 넣는 것이다.

  • 필요성: 회원가입 함수를 짠다. 중복 회원인지 확인하려고 진짜 DB를 붙여서 테스트를 돌리면 2가지 재앙이 온다. 첫째, DB 네트워크를 타느라 테스트 하나당 2초가 걸려 1만 개를 돌리면 하루가 다 간다. 둘째, 어제는 가입에 성공했는데 오늘은 누가 진짜 DB에 똑같은 이름("Kim")을 박아놔서 내 테스트가 억울하게 실패(Fail)한다. 외부 환경(DB 데이터)의 오염 때문에 내 완벽한 로직(회원가입 코드)이 틀렸다고 오해받는 '결정론적 실패'를 막으려면, 무조건 정해진 대답만 기계처럼 뱉어주는 격리된(Isolated) 거짓말쟁이(Stub)가 절대적으로 필요하다.

  • 💡 비유: Stub은 연극 연습용 **'자동 대사 재생기'**와 같습니다. 주인공(내 로직)이 상대방 배우(DB)와 합을 맞춰야 하는데, 상대 배우가 지각을 했습니다. 연습을 멈출 순 없으니, 버튼을 누르면 상대방의 대사("네, 알겠습니다")가 녹음기에서 똑같이 흘러나오게 조작해 둔 기계가 바로 Stub입니다. 상대 배우가 진짜 사람이든 녹음기든 주인공은 자기 대사 연습을 100% 완벽하게 할 수 있습니다.

  • 등장 배경 및 발전 과정:

    1. 통합 테스트의 피로도: 과거엔 무조건 진짜 DB를 띄우고(Setup), 데이터 1만 건을 밀어 넣은 뒤, 테스트 끝나면 다 지워야(Teardown) 하는 극한의 노가다를 했다.
    2. 가짜 객체의 수작업 코딩: 짜증이 난 개발자들이 테스트 파일 밑에 class FakeDB { return "OK"; } 라며 임시 클래스를 수작업으로 타이핑하기 시작했다.
    3. Mockito 흑마법의 도래 (현재): 수작업 클래스 짜는 것도 귀찮다. 현재는 자바 진영의 Mockito 같은 프레임워크가 등장해, 코드 1줄(when(db.getUser()).thenReturn("Kim"))만 적으면 런타임에 알아서 가짜 로봇(Stub)을 허공에서 연성해 내는 시대로 진화했다.
  • 📢 섹션 요약 비유: Stub을 쓰는 것은, 전투기 파일럿(내 코드)이 진짜 미사일을 쏘며 비행하는 대신, '비행 시뮬레이터(가짜 윈도우)' 안에서 연습하는 것과 같습니다. 스크린에서 "적기 출현!"이라는 가짜 상황(Stub 응답)을 띄워주면, 파일럿은 진짜 전투기처럼 똑같이 스위치를 누릅니다. 파일럿의 조작 능력을 돈 안 들이고 안전하게 검증하는 완벽한 가상현실입니다.


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

1. Stub의 동작 아키텍처 및 코드 구조

단위 테스트를 완벽하게 통제된 상태로 끌고 가는 3단계 셋업 구조다.

[ Mockito를 활용한 Stub 세팅의 정석 ]

// 1. Arrange (준비) : 스텁 로봇의 대본(상태)을 세팅한다.
// "누군가 userRepository의 find() 함수를 부르면, 무조건 '철수' 객체를 뱉어라!"
when(userRepository.find("id-123")).thenReturn(new User("철수"));

// 2. Act (실행) : 진짜 내 비즈니스 로직을 때린다.
// userService는 내부에서 DB를 부르지만, 진짜 DB가 아니라 아까 세팅한 로봇(Stub)이 대답한다.
String result = userService.getUserName("id-123"); 

// 3. Assert (검증 - 상태 검증) : 내 로직이 뱉은 최종 결과(State)가 맞는지 확인한다.
assertEquals("철수", result);
  • 상태 검증(State Verification): 위 코드를 보면, 테스트의 끝판왕(Assert)이 userService가 만들어낸 **"결괏값(철수)"**이 맞는지 점검한다. 즉, 스텁(Stub)은 중간 과정에서 징검다리 역할로 힌트만 던져줄 뿐, 최종 감시는 내 로직의 '상태(데이터)'에 쏠려 있다. 이것이 Stub을 규정하는 핵심 철학이다.

2. 예외(Exception) 강제 주입: Stub의 진정한 무기

Stub이 단순히 "정상 데이터"만 뱉는 용도라면 반쪽짜리다. 해커와 버그를 막기 위해 극단적 엣지 케이스를 강제할 때 진가를 발휘한다.

// "카카오페이 결제 API를 불렀을 때, 갑자기 타임아웃(Timeout) 500 에러를 뿜게 만들어라!"
when(kakaoPay.charge()).thenThrow(new TimeoutException());

// 이 극단적인 재난 상황에서, 내 로직이 뻗지 않고 우아하게 "결제 실패 화면"을 그리는지 테스트!
assertThrows(PaymentFailedException.class, () -> myService.pay());
  • 📢 섹션 요약 비유: 이 과정은 소방관에게 **'가짜 불(Exception) 지르기 훈련'**을 하는 것과 같습니다. 진짜 빌딩에 불을 낼 순 없으니, 소방 훈련장(Stub)에 연막탄을 팍 터뜨리고(강제 에러 발생) 소방관(내 코드)이 기절하지 않고 매뉴얼대로 산소호흡기를 차고 빠져나오는지(예외 처리) 극한의 맷집을 안전하게 확인하는 기술입니다.

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

1. Stub (스텁) vs Dummy (더미) vs Mock (목)의 삼각 관계

테스트 더블 삼형제의 역할을 명확히 찢어내야 아키텍트다.

대역 배우행동(Behavior)의 능동성실패(Fail) 발생 지점주 사용 목적
Dummy아무것도 안 함. 부르면 즉사함.테스트 로직과 상관없음파라미터 개수 채우기 (자리만 차지)
Stub내가 훈련시킨 대답(값/에러)만 줌맨 마지막 assertEquals (값 비교 시)상태 검증 (내 로직의 결과가 맞나?)
Mock네가 대본대로 함수를 불렀나 감시함함수가 불리는 순간 즉각 터짐행위 검증 (내 코드가 남의 함수를 몇 번 찔렀나?)

과목 융합 관점

  • 네트워크 (MSA 환경의 와이어목, WireMock): 코드 레벨에서 Mockito로 스텁을 짜는 것을 넘어, 마이크로서비스(MSA) 시대에는 서버 대 서버 통신 자체가 지옥이다. 이땐 코드 밖 인프라에 WireMock이라는 가짜 웹 서버(독립된 Stub)를 띄워놓는다. 내가 http://fake-api.com/pay로 진짜 HTTP 패킷을 쏘면, 와이어목 서버가 세팅된 대로 {"status": 200} 이라는 JSON을 스텁으로 내려준다. 코드 레벨의 Stub이 인프라 레벨의 Stub으로 거대하게 진화한 융합 아키텍처다.

  • 클린 아키텍처 (육각형 아키텍처): Stub은 헥사고날(Hexagonal) 아키텍처의 포트(Port)와 어댑터(Adapter) 패턴을 강제한다. 내 로직이 OracleDB라는 구체적인 콘크리트 클래스에 박혀 있으면 스텁(가짜)을 꽂아 넣을 구멍이 없다. 즉, 개발자가 스텁을 쓰기 위해 발버둥 치다 보면, 자연스럽게 DB를 Repository라는 인터페이스(포트)로 뽑아내게 되고, 핵심 비즈니스 로직은 프레임워크나 DB 기술로부터 완벽하게 독립(Clean)되는 경이로운 아키텍처의 선순환을 낳는다.

  • 📢 섹션 요약 비유: 더미는 질문하면 뺨을 때리는 바보이고, 스텁은 "네, 아니오"라는 객관식 정답 카드를 들고 있는 앵무새이며, **목(Mock)**은 내 뒤에 서서 "너 방금 A 질문 안 하고 B 질문했지?"라며 내 행동거지를 채점하는 무서운 감독관입니다.


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

실무 시나리오

  1. 시나리오 — 시간(Time) 객체의 종속성에 빠진 치명적 버그: 어떤 개발자가 "밤 12시에 접속하면 야간 할인을 적용한다"는 로직을 짜고 테스트를 만들었다. 테스트 안에는 LocalDateTime.now()가 들어 있었다. 개발자가 밤에 야근하며 테스트를 짰을 땐 초록불(Pass)이 떠서 배포했다. 그런데 다음 날 낮 12시에 CI 서버(Jenkins)가 테스트를 돌리자 "할인 안 됨"이라며 일제히 테스트가 터져서 빌드가 마비되었다. 시간이 지나면 결과가 바뀌는 비결정적(Non-deterministic) 테스트의 재앙이다.

    • 아키텍트의 해결책: 통제 불가한 외부 자원(시간)의 Stub 주입화다. 아키텍트는 코드 내부에 now()를 쌩으로 박는 짓(하드코딩)을 엄격히 금지해야 한다. 시간 자체를 뱉어내는 TimeProvider 인터페이스를 만들고(의존성 주입), 테스트 환경에서는 when(timeProvider.now()).thenReturn("밤 12시")라고 시간조차 스텁(Stub)으로 고정시켜버려야 한다. 그래야 낮에 돌리든, 10년 뒤에 돌리든 무조건 초록불이 뜨는 완벽히 통제된 실험실이 완성된다. (랜덤 난수(Random) 생성기도 100% 동일하게 스텁 처리 대상이다.)
  2. 시나리오 — 스텁 떡칠(Over-stubbing)로 인한 의미 없는 테스트 통과: 주니어 개발자가 결제 후 이메일 발송 기능의 테스트를 짰다. 결제 DB 저장도 스텁, 이메일 API 전송도 스텁, 잔액 확인도 스텁으로 싹 다 "무조건 OK 리턴해"라고 훈련시켜 놨다. 그리고 마지막에 assertEquals("OK", result)를 짰다. 테스트는 0.1초 만에 완벽하게 100점을 맞았다. 하지만 라이브에서 로직 순서가 다 꼬여 결제도 안 됐는데 이메일이 발송되는 사고가 터졌다.

    • 아키텍트의 해결책: 행위 검증(Mock) 부재로 인한 논리적 빵꾸다. 스텁(Stub)은 "이 값을 줄 테니 네 로직 계산 결과가 뭐야?"를 묻는 도구다. 하지만 'DB 저장'이나 '이메일 발송'처럼 값을 뱉는 게(Return) 아니라 행위 자체가 파괴적인 부수 효과(Side-effect)를 일으키는 구간에서는 스텁을 쓰면 안 된다. "순서대로 정확히 1번 찔렀는가?"를 감시하는 **Mock(행위 검증)**을 섞어 써야만, 가짜 값만 앵무새처럼 뱉고 넘어가는 '스텁의 독'을 해독할 수 있다.

도입 체크리스트

  • 비즈니스적: 외부 API(PG사 등) 과금 및 속도 문제를 해결했는가? 팀원 10명이 하루에 테스트를 1만 번 돌리는데 진짜 외부 결제 API를 붙여놨다면 네트워크 비용과 속도 딜레이로 회사가 파산한다. 연동되는 모든 서드파티(Third-party) 통신 구간은 무조건 예외 없이 Stub(와이어목 등)으로 잘라내어, 우리 회사의 빌드 파이프라인이 100% 자급자족으로 돌아가게 방화벽을 쳐야 한다.
  • 기술적: TDD의 '빠른 피드백(Fast Feedback)' 룰을 수호하고 있는가? 단위 테스트 1,000개가 10초를 넘어가면 개발자는 테스트 돌리기를 귀찮아하고 TDD는 버려진다. DB 접속(JPA/MyBatis)을 띄우는 순간 10초는 무조건 깨진다. 아키텍트는 데이터 액세스 계층과 비즈니스 로직 계층을 칼같이 자르고, 비즈니스 계층 테스트에는 100% Mockito 스텁을 주입하여 메모리 위에서 0.5초 만에 전체 테스트가 광속으로 돌아가게 튜닝해야 한다.

안티패턴

  • 운영 코드에 테스트 냄새 묻히기 (Test Logic in Production): 밖에서 스텁을 꽂을 실력이 부족해서, 아예 운영(Production) 코드 안에 public String getAge() { if(isTestMode) return 25; return realDB.getAge(); } 라고 끔찍한 분기문을 심어놓는 대참사. 해커가 이 틈을 타고 파라미터를 조작해 테스트 모드로 돌려버리면 라이브 서비스가 전부 가짜 값을 뱉으며 박살이 난다. 테스트를 위한 임시 처방(Stub)은 무조건 프레임워크 밖에서 꽂아라.

  • 📢 섹션 요약 비유: 시간에 무지성으로 얽매인 테스트는, **'비 올 때만 켜지는 고장 난 자동차 와이퍼'**와 같습니다. 와이퍼 고장을 고치려면 정비사가 호스(Stub)로 가짜 비를 뿌려가며 통제된 상황에서 고쳐야지, 진짜 비가 올 때까지 일주일 내내 기다렸다가 고치는(진짜 시간에 의존) 바보는 없습니다. 통제력을 얻기 위한 가짜 데이터 주입, 그것이 스텁의 본질입니다.


Ⅴ. 기대효과 및 결론

정량/정성 기대효과

구분외부 자원(DB/API)에 의존하는 결합 테스트 (AS-IS)모든 외부 의존성을 Stub으로 격리한 단위 테스트 (TO-BE)개선 효과
정량진짜 DB 연동으로 단위 테스트 1,000개 수행 5분 소요메모리 기반의 Stub 주입으로 1,000개 수행 2초 소요CI 빌드 및 테스트 소요 시간 99% 광속 단축
정량타 팀 서버 타임아웃으로 인한 억울한 테스트 실패 30%통제된 Stub 데이터 응답으로 테스트 100% 결정론적 통과환경 오염(Flaky Test)으로 인한 결함 디버깅 비용 제로화
정성"어제는 성공했는데 왜 오늘은 에러가 나지?" 불신 팽배"이 방(로직) 안에서 일어나는 일은 100% 내 통제하에 있어"테스트 슈트(Test Suite)에 대한 엔지니어의 절대적 신뢰 구축

미래 전망

  • 계약 주도 테스트(Contract Testing, Spring Cloud Contract)의 융합: MSA 시대에 Stub이 가진 치명적 단점은 "내가 짠 스텁(가짜) 데이터가, 나중에 상대방 진짜 서버의 API 모양과 달라지면 어떡하지?"라는 거짓말의 공포다. 이를 막기 위해 양쪽 서버가 "우리 이런 JSON 던질게"라고 계약서(Contract) 파일 하나를 공유하고, 그 계약서를 기반으로 기계가 스텁 서버를 100% 완벽하게 똑같이 자동 복제해 주는 '계약 기반 스텁 자동화' 시대가 금융권 표준으로 자리 잡고 있다.
  • AI 엣지 케이스 자동 스터빙: 깃허브 코파일럿(Copilot)이 이제는 정상 값(200 OK)만 스텁으로 짜주지 않는다. 코드를 쓱 스캔하고 "너 결제 모듈 연동하네? 내가 무작위로 '잔액 부족 에러(402)', '카드 한도 초과 에러(500)' 스텁 로직을 10개 짜줄 테니까 방어 로직 짜봐"라고 개발자의 상상력을 넘어선 예외 상황을 선제적으로 주입해 주는 AI 조력자 시대로 진화 중이다.

참고 표준

  • Martin Fowler의 "Mocks Aren't Stubs": 소프트웨어 공학의 거장 마틴 파울러가 스텁(상태 검증)과 목(행위 검증)의 철학적 차이를 명확히 갈라치며 전 세계 개발자의 논쟁을 종식시킨 전설적인 아티클.
  • Deterministic Test (결정론적 테스트): TDD의 절대 헌장. "똑같은 소스코드를 똑같은 테스트로 100번 돌리면, 맑은 날이든 비 오는 날이든 무조건 100번 다 똑같이 성공(또는 실패)해야 한다." 이것을 가능하게 해주는 유일한 무기가 바로 Stub이다.

스텁(Stub)은 소프트웨어 공학에서 '진정한 자유(Independence)'를 선언하는 독립 만세문이다. 내 코드(로직)의 훌륭함을 증명하기 위해 왜 남의 서버(DB, API) 눈치를 보며 굽신거려야 하는가? 기술사는 이 지긋지긋한 강한 결합(Coupling)의 사슬을 끊어버리고, 인터페이스라는 단검으로 경계를 자른 뒤, 나만을 위해 절대 복종하는 기계 로봇(Stub)을 그 자리에 꽂아 넣어 무균 상태의 완벽한 실험실을 세워야 한다. 세상이 폭주하고 네트워크가 불타오를 때도, 당신의 테스트 로직만큼은 0.01초 만에 묵묵히 초록불(Green)을 띄우며 아키텍처의 무결성을 증명해 내는 우아한 고독, 그것이 스텁이 빚어내는 통제의 예술이다.

  • 📢 섹션 요약 비유: 스텁을 안 쓰는 테스트는 **'바다에 띄운 종이배'**입니다. 내가 아무리 예쁘게 접어도 파도(남의 서버)가 치면 뒤집혀버리니 내 실력을 증명할 수 없습니다. 스텁을 쓰는 테스트는 **'집 안의 목욕탕에 띄운 종이배'**입니다. 바닷물(가짜 데이터)을 받아놓고 선풍기로 바람(가짜 에러)을 만들어내며, 100% 내가 통제하는 환경에서 내 종이배의 맷집과 속도를 완벽하고 조용하게 측정해 내는 위대한 실험실입니다.

📌 관련 개념 맵 (Knowledge Graph)

개념 명칭관계 및 시너지 설명
더미 (Dummy)파라미터 숫자만 맞추기 위해 끼워 넣은 마네킹. 만약 이 마네킹 입에 대사("100원")를 물려주고 말하게 시킨다면 그놈은 스텁(Stub)으로 진화한 것이다. (이전 장 459번)
목 (Mock)스텁의 영원한 라이벌. 스텁이 "이 값 받아가!"라며 값(상태)에만 신경 쓴다면, 목은 뒷짐 지고 "너 방금 저 함수 몇 번 불렀냐?"라며 행동 거지(행위)를 깐깐하게 채점하는 교관. (다음 장 462번)
멱등성 (Idempotency)1번 돌리나 1만 번 돌리나 결과가 똑같은 성질. 스텁(Stub)이 똑같은 가짜 값을 1만 번 무지성으로 리턴해주기 때문에, 단위 테스트는 완벽한 멱등성을 얻게 된다.
의존성 역전 원칙 (DIP)스텁(로봇)을 내 몸에 꽂기 위한 선결 조건. 콘크리트 구현체(오라클 DB) 대신 껍데기(인터페이스)만 바라보게 코드를 뜯어고쳐야 스텁을 주사기로 꽂아 넣을 수 있다.
계약 주도 테스트 (Contract Test)헛똑똑이 스텁의 맹점(진짜 API 스펙이 바뀌었는데 낡은 스텁만 믿고 통과하는 사기극)을 원천 차단하기 위해, 양 팀이 계약서를 쓰고 스텁을 기계가 자동 갱신해 버리는 융합 테스팅.

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

  1. 내가 로봇 조립 공장 사장님인데, '로봇 머리'가 불빛을 찰칵찰칵 잘 내는지 검사(테스트)하고 싶어요.
  2. 그런데 무겁고 밧데리도 많이 먹는 '진짜 로봇 몸통'을 매번 밑에 끼워서 검사하려니까 너무 힘들고 시간도 오래 걸렸어요.
  3. 그래서 몸통은 떼어버리고, 그냥 전기만 찌릿! 하고 똑같이 올려주는(가짜 응답값) 작고 가벼운 가짜 배터리 상자를 머리에 꽂아서 1초 만에 검사를 끝내버렸어요. 이 똑똑한 가짜 상자를 **'스텁(Stub)'**이라고 부른답니다!