테스트 더블 (Test Double)

테스트 더블(Test Double) 은 xUnit Test Patterns의 저자 Gerard Meszaros가 만든 용어로,
소프트웨어 테스트에서 실제 객체를 대체하는 가짜 객체를 통칭하는 개념입니다.
실제 DB나 외부 API와 상호작용하는 코드는 테스트하기 무겁고 제어하기 어렵기 때문에,
테스트 더블을 사용해 테스트를 독립적이고 빠르게 만들 수 있습니다.
테스트 더블이 필요한 이유
실제 환경 의존성 제거: DB, 네트워크, 외부 API 없이 테스트 가능
테스트 속도 향상: 인메모리 객체 사용으로 I/O 부담 감소
예측 가능한 테스트: 외부 서비스 상태와 무관하게 결과 고정
특정 상황 시뮬레이션: 에러/타임아웃 등 비정상 케이스 재현 가능
테스트 더블의 종류 (Gerard Meszaros 분류)
1️⃣ Dummy
인스턴스는 필요하지만 동작은 전혀 필요 없는 객체
파라미터를 채우기 위해서만 사용되고, 실제 로직에서는 쓰이지 않음
호출되어도 정상 동작 보장하지 않음
class NotificationService {
public void sendNotification(User user) {
// 테스트에서 user는 사용되지 않음
}
}
@Test
void dummyTest() {
NotificationService service = new NotificationService();
User dummyUser = new User(); // 아무 동작 없는 더미 객체
service.sendNotification(dummyUser);
}
사용 포인트: 단순히 객체만 필요할 때
2️⃣ Fake
실제와 유사하게 동작하지만 단순화된 구현
동작은 하지만 프로덕션 환경에서는 적합하지 않음
예: 인메모리 DB, 메모리 기반 Repository
interface UserRepository {
User save(User user);
Optional<User> findById(Long id);
}
// 인메모리 Fake 구현
class FakeUserRepository implements UserRepository {
private final Map<Long, User> store = new HashMap<>();
public User save(User user) { store.put(user.getId(), user); return user; }
public Optional<User> findById(Long id) { return Optional.ofNullable(store.get(id)); }
}
@Test
void fakeTest() {
UserRepository repo = new FakeUserRepository();
repo.save(new User(1L, "홍길동"));
assertTrue(repo.findById(1L).isPresent());
}
사용 포인트: DB 없이 빠른 테스트를 하고 싶을 때
3️⃣ Stub
고정된 값을 반환하는 테스트용 구현
외부 API나 Repository 호출 결과를 항상 같은 값으로 응답하게 함
테스트를 예측 가능하고 안정적으로 만듦
public class StubUserRepository implements UserRepository {
// ...
@Override
public User findById(long id) {
return new User(id, "Test User");
} // 동일한 id 값에 Test User라는 값을 가진 고정된 User 인스턴스 반환받음
}
사용 포인트: 특정 값으로 테스트를 고정하고 싶을 때
4️⃣ Mock
행위(Behavior) 검증을 위한 객체
메서드가 올바르게 호출됐는지(횟수/인자) 검증
verify()로 호출 여부를 체크 → 결과값이 아니라 행위 자체를 테스트
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {
@Mock PaymentGateway paymentGateway; // Mock 객체
@InjectMocks OrderService orderService;
@Test
void mockTest() {
orderService.placeOrder(500);
// pay() 메서드가 정확히 한 번 호출됐는지 검증
verify(paymentGateway, times(1)).pay(anyInt());
}
}
사용 포인트: 특정 메서드가 기대한 대로 호출됐는지 확인할 때
5️⃣ Spy
Stub + 호출 기록을 남기는 객체
Stub처럼 값도 반환하면서, 메서드 호출 횟수나 인자를 기록
테스트 종료 후 검증에 활용
class SpyEmailService implements EmailService {
private int sendCount = 0;
public void send(String to, String message) {
sendCount++;
}
public int getSendCount() { return sendCount; }
}
@Test
void spyTest() {
SpyEmailService spy = new SpyEmailService();
UserService service = new UserService(spy);
service.registerUser("test@test.com");
assertEquals(1, spy.getSendCount()); // 호출 횟수 검증
}
사용 포인트: 호출 내역(횟수, 인자)을 검증하고 싶을 때
요약
| 종류 | 특징 | 사용 예시 |
| Dummy | 동작 없음, 인스턴스만 필요 | 파라미터 채우기 |
| Fake | 단순화된 실제 구현 | 인메모리 DB |
| Stub | 고정된 응답 반환 | 외부 API 대체 |
| Mock | 행위 검증 | verify()로 호출 여부 체크 |
| Spy | Stub + 호출 기록 | 호출 횟수 검증 |
한 줄 요약
테스트 더블은 실제 객체 대신 사용하는 가짜 객체이며, 테스트의 독립성과 안정성을 높이기 위해 사용됩니다.
목적에 따라 Dummy, Fake, Stub, Mock, Spy로 나뉘며, 각기 다른 테스트 시나리오에서 활용됩니다.
출처:https://tecoble.techcourse.co.kr/post/2020-09-19-what-is-test-double/