단위 테스트, 값 넣고 값 나오면 되는 거 아니야?
서론
테스트 코드를 처음 접했을 때, 이런 생각이 들었다.
“단위 테스트? 그냥 값 넣고 값 나오면 되는 거 아닌가?”
그렇게 생각하고 @Mock도 쓰고, @InjectMocks도 쓰면서 뿌듯하게 테스트를 짰다.
하지만 단위 테스트, 통합 테스트, E2E 테스트까지 공부해보니 내가 하고 있던 게 진짜 단위 테스트가 아니었다는 걸 알게 됐다.
이 글은 그런 과정을 통해 내가 직접 겪고 정리한 테스트에 대한 이해를 담고 있다.
부족한 내용이 있을 수 있지만, 나처럼 막 시작한 사람에게는 도움이 될 거라 생각한다.
📚 테스트 더블(Test Double) 역할별 정리
테스트 더블은 테스트 대상 객체가 의존하는 외부 객체들을 대신하는 가짜 객체입니다.
실제 구현 대신 테스트 목적에 맞게 동작을 흉내 내어 테스트를 빠르고 안정적으로 수행할 수 있도록 돕습니다.
역할별 설명
1. Dummy
- 테스트 코드에서 인자가 필요하지만 실제로 테스트에 사용되지 않는 경우
- 내부 로직이나 상태와 무관하게 단순히 자리를 채우는 역할
User dummyUser = new User(null, null); // 실제로 값은 사용하지 않음
2. Stub
- 특정 메서드 호출에 대해 미리 정해진 응답을 반환
- 상태 기반 테스트에 적합하며, 테스트 대상이 의존하는 객체의 행동을 통제 가능
- Mockito 기준 when().thenReturn() 사용
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
UserRepository userRepository;
@InjectMocks
UserService userService;
@Test
void chargePoint_ShouldThrow_WhenUserNotFound() {
// Stub: userId를 넣으면 무조건 null 반환되게 설정
when(userRepository.findByUserId("invalidUser")).thenReturn(Optional.empty());
assertThrows(CoreException.class, () -> {
userService.chargePoint("invalidUser", 1000);
});
}
}
3. Mock
- 특정 메서드가 호출되었는지, 몇 번 호출되었는지 검증하는 행위 기반 테스트
- 메서드 호출 여부나 횟수 검증에 초점
- Mockito 기준 verify() 사용
@ExtendWith(MockitoExtension.class)
class UserServiceMockTest {
@Mock
UserRepository userRepository;
@InjectMocks
UserService userService;
@Test
void findUser_ShouldCallRepositoryOnce() {
User user = new User("testUser", "test@email.com");
when(userRepository.findByUserId("testUser")).thenReturn(Optional.of(user));
userService.findUser("testUser");
// Mock: 해당 메서드가 한 번 호출되었는지 검증
verify(userRepository, times(1)).findByUserId("testUser");
}
}
정리하며
- 단위 테스트는 외부 의존성을 격리한 상태에서 단일 클래스/메서드의 비즈니스 로직을 검증해야 한다.
- 상태 검증에는 Stub, 호출 행위 검증에는 Mock을 상황에 맞게 사용하는 것이 핵심이다.
테스트 더블의 개념을 확실히 잡고 나니, 이전보다 훨씬 빠르고 명확한 단단한 단위 테스트를 작성할 수 있게 되었다.
함께 읽으면 좋은 글
- Redis Cache Stampede 현상 극복기: 캐시 적용하면 DB 부하 끝 아닌가?
- JPA N+1 문제와 Fetch Join 실무 고찰: lazy loading이면 다 해결되나?
- PostgreSQL FOR UPDATE SKIP LOCKED: DB 락 경합 없이 고성능 작업 큐 구축하기
공식 문서와 확인 자료
아래 1차 자료를 기준으로 명령 동작과 적용 조건을 다시 확인할 수 있습니다.