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을 사용하여 테스트의 신뢰성과 유연성을 동시에 확보하는 것이 중요합니다.