개요
테스트 코드를 공부하면서 외부 의존성을 간편하게 대체하여 테스트를 도와주는 Mock, Spy를 자주 사용해왔다. 하지만 그 둘의 차이를 명확하게 알지 못한 채 사용해왔다. 이런 객체를 아우르는 “테스트 더블(Test Double)” 이라는 용어를 살펴보고, 테스트 더블의 종류와 차이에 대해서 알아보려고 한다.
목차
테스트 더블(Test Double)이란?
테스트 더블(Test Double)
은 실제 시스템의 구성요소(예: 객체, 모듈 등)를 테스트 환경에서 대신 사용하는 객체를 의미한다.
예를 들어, 외부 SDK나 복잡한 내부 모듈에 의존하는 코드를 테스트할 때, 해당 모듈을 직접 구현하거나 실행하지 않고도 필요한 동작이나 호출 정보를 사전에 정의하여 테스트 코드를 실행할 수 있다.
이를 통해 테스트 범위를 특정 부분으로 좁힐 수 있으며, 테스트 속도를 높이고 테스트의 신뢰성을 확보할 수 있다. 또한, 실제 모듈을 사용할 경우 발생할 수 있는 요금 부과, 데이터 변경, 외부 시스템 호출 등의 부작용도 피할 수 있다.
테스트 더블은 아직 개발되지 않은 의존 객체를 임시로 대체할 때 유용하다. 또한, 예외 상황이나 오류 케이스를 인위적으로 재현해야 하는 경우에도 효과적으로 사용할 수 있다.
테스트 더블의 종류
1. 더미(Dummy)
사용되지 않지만 인자를 채우기 위해 사용한다. 빈 객체 또는 빈 함수가 이에 해당한다.
type Func = (a: () => void, b: Record<string, number>) => string;
const func: Func = (a, b) => {
// 내부 구현
};
/**
* 빈 함수, 빈 객체를 인자로 넘김.
* 함수의 인자로 넘긴 빈 함수, 빈 객체를 "더미"라고 한다.
* */
func(() => {}, {});
2. 스텁(Stub)
정해진 값을 반환하는 함수를 구현한 테스트 더블이다. MSW에서 정해진 응답을 내려주는 handler나 아래의 예시 코드처럼 항상 특정 값을 반환하는 함수/객체를 의미한다.
function stubFunc() {
return "Stefan";
}
3. 스파이(Spy)
스텁의 특징 + 호출 정보를 가지는 테스트 더블. 함수라면, 인자를 어떻게 전달했는지. API의 경우엔 request 정보 등을 의미한다.
const spy = jest.fn();
spy("a");
expect(spy).toHaveBeenCalledWith("a"); // true
const td = jest.fn().mockImplementation((x) => x * 2);
// stub
expect(td(2)).toBe(4);
// spy
expect(td(2)).toHaveBeenCalled();
4. 목(Mock)
테스트 대상이 의존 객체와 상호작용하는지를 확인하기 위해서 사용하는 테스트 더블. 특정 이벤트나 로직을 실행할 때, 특정 함수를 호출했는지 + 어떻게(인자, 호출 횟수 등) 호출했는지를 검증한다.
5. 페이크(Fake)
실제 동작하지만, 테스트용으로 단순화한 테스트 더블이다. 예시로는 인메모리 DB가 있다.
Mock vs Stub
Stub과 비교를 해보자. Stub은 반환값을 사용하여 출력 결과를 검증한다. 테스트 대상이 예상한 값(상태)를 출력했는지
를 검증한다. 반면, Mock은 어떻게 호출했는지
를 검증한다. 테스트 대상이 예상한대로 행동했는지를 검증한다.
Mock과 Stub의 차이
항목 | Stub | Mock |
---|---|---|
결과 검증 | 결과만 검증 | 호출된 방식 검증 |
검증 시점 | 테스트 끝나고 단순 assert | Mock 객체가 스스로 검증 수행 |
테스트 관점 | 상태 중심 (state) | 행동 중심 (behavior) |
관심사 | “결과가 맞는가?” | “호출이 제대로 되었는가?” |