Ruby 테스트의 핵심 개념: Double, Stub, Mock 이해하기

The Three Musketeers: Double, Stub & Mock

작성자
발행일
2025년 09월 11일

핵심 요약

  • 1 Double, Stub, Mock은 Ruby 테스트에서 외부 의존성을 격리하고 특정 동작을 검증하는 데 사용되는 가짜 객체입니다.
  • 2 Stub은 메서드 호출 시 고정된 반환 값을 정의하여 예측 가능한 테스트를 돕고, Mock은 메서드 호출 여부와 인자까지 검증하여 중요한 상호작용을 보장합니다.
  • 3 테스트의 신뢰성과 유연성을 위해 중요한 상호작용은 Mock으로, 부수적인 효과는 Stub으로 처리하는 균형 잡힌 접근 방식이 중요합니다.

도입

Ruby에서 테스트를 작성하는 것은 즐거운 경험일 수 있지만, Double, Stub, Mock과 같은 핵심 개념이 명확하지 않으면 어려움을 겪기 쉽습니다. 특히 테스트 실패의 원인을 즉시 파악하기 어려울 때 좌절감을 느끼는 경우가 많습니다. 본 글은 'Eloquent Ruby'를 읽으며 저자가 느꼈던 개념적 혼란에서 시작되었으며, 이 세 가지 테스트 개념을 명확히 이해하고 실질적인 Ruby 예제와 함께 쉽게 설명하는 것을 목표로 합니다. 이를 통해 개발자들이 테스트 작성 시 겪는 어려움을 줄이고 코드에 대한 확신을 가질 수 있도록 돕고자 합니다.

Ruby 테스트에서 Double, Stub, Mock은 외부 의존성을 관리하고 특정 동작을 검증하는 데 필수적인 도구입니다. 각각의 역할과 사용법은 다음과 같습니다.

Double (더블)

  • 정의: 실제 객체를 대신하는 가짜 객체입니다.

  • 목적: API, 데이터베이스, 이메일 서비스와 같은 외부 의존성으로부터 테스트를 격리하여 테스트를 빠르고 안정적이며 부작용이 없게 만듭니다.

  • 예시: user = double("User"), allow(user).to receive(:name).and_return("Ender")

Stub (스텁)

  • 정의: 특정 메서드 호출 시 고정된 반환 값을 정의합니다. “이 메서드가 호출되면 항상 이 특정 결과를 반환하라”는 의미입니다.

  • 특징: 메서드 호출 여부 자체보다는 호출될 경우의 반환 값에만 관여합니다.

  • 장점: API 호출이나 데이터베이스 쿼리 같은 외부 요소를 격리하여 테스트를 빠르고 예측 가능하게 만듭니다.

  • 단점: 스텁된 메서드가 실제로는 호출되지 않아도 테스트가 통과할 수 있어, 중요한 동작 누락을 놓칠 수 있습니다.

  • 예시: allow(calculator).to receive(:add).with(2, 3).and_return(5)

Mock (목)

  • 정의: Stub과 유사하지만, 메서드가 테스트 중에 실제로 호출되어야 한다는 추가적인 기대를 포함합니다. 호출되지 않으면 테스트가 실패합니다. “이 메서드를 이 정확한 인수로 호출하라”는 의미입니다.

  • 장점: 이메일 전송이나 결제 API 트리거와 같은 중요한 상호작용이 실제로 발생하는지 보장합니다.

  • 단점: Mock을 과도하게 사용하면 테스트가 취약해질 수 있습니다. 내부 구현의 작은 변경(예: 다른 로거 메서드 호출)에도 테스트가 실패할 수 있습니다.

  • 예시: expect(notifier).to receive(:send_email).with("ender@example.com")

실제 예시: PaymentService

결제 서비스 (PaymentService)가 결제 처리 후 사용자에게 이메일을 보내고 로그를 기록하는 시나리오에서, user, notifier, logger는 Double로, logger.info는 Stub으로(반환 값만 정의), notifier.send_email은 Mock으로(호출 여부와 인자 검증) 사용됩니다.

  • Double: user, notifier, logger (가짜 객체)

  • Stub: allow(logger).to receive(:info).and_return(true) (로그는 실제 동작하지 않고 값만 반환)

  • Mock: expect(notifier).to receive(:send_email).with(...) (이메일 전송 호출을 강제)

잘못된 사용과 균형 잡힌 접근

  • Stub만 사용: 이메일 전송과 같은 중요한 동작이 실제로 발생하지 않아도 테스트가 통과하여 중요한 검증을 놓칠 수 있습니다.

  • Mock만 사용: 사소한 내부 변경에도 테스트가 실패하여 테스트가 너무 취약해질 수 있습니다.

  • 최적의 접근: 이메일 알림, API 호출 등 중요한 동작에는 Mock을 사용하고, 로깅이나 메트릭과 같은 덜 중요한 부수 효과에는 Stub을 사용하여 테스트의 신뢰성과 유연성을 동시에 확보하는 것이 중요합니다.

결론

Double, Stub, Mock은 Ruby 테스트의 삼총사처럼 각기 고유한 역할을 수행하며, 현명하게 함께 사용될 때 테스트 스위트를 더 깔끔하고 빠르며 신뢰할 수 있게 만듭니다. Double은 실제 객체의 대리자 역할을 하고, Stub은 외부 의존성을 격리하기 위해 고정된 반환 값을 제공하며, Mock은 중요한 상호작용이 실제로 발생하는지 보장합니다. 핵심은 균형입니다. 이메일, 결제, API 호출 등 정말 중요한 것은 Mock으로, 로깅이나 메트릭처럼 중요도가 낮은 것은 Stub으로 처리하고, Double을 활용하여 테스트를 가볍게 유지해야 합니다. 이 세 가지 개념을 명확히 이해함으로써 모호한 테스트 실패로 인한 좌절감을 줄이고, 코드에 대한 확신을 가지고 빠르고 효율적으로 테스트를 작성할 수 있을 것입니다.

댓글 0

로그인이 필요합니다

댓글을 작성하거나 대화에 참여하려면 로그인이 필요합니다.

로그인 하러 가기

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!