테스트 격리성의 중요성과 모의 객체(Mock Object)를 활용한 전략
@MockBean과 @Mock의 차이 (+ @InjectMocks)
위 글에서 다음과 같은 내용의 댓글이 달렸다.
테스트 중에는 AuthService의 실제 로직이 실행되면서, 의존하는 컴포넌트는 제어 가능한 모의 객체로 대체되어 테스트의 격리성을 확보할 수 있다. 여기서 테스트의 격리성이 확보된다는게 무슨 의미인가요?
목객체로 의존성이 주입되어서 메서드는 실제로 실행이 안되는것으로 아는데 when을 사용해서 목객체의 값을 세팅해서 sut내부의 메서드에 대한 동작 테스트를 하는 방식이 격리성을 확보한다는 의미인가요? (의존성 주입 없이 완전히 서비스 클래스만 격리해서 테스트)
📌 답변
먼저, 글을 읽어주고 궁금증을 공유해줘서 감사하다. 이 댓글은 팀원이 달아준 내용인데 "테스트의 격리성"에 대한 질문은 중요한 주제고, 이를 명확하게 이해하는 것이 효과적인 테스트 전략을 수립하는 데 도움이 된다고 생각하기 때문에 따로 정리해서 다 같이 공유해보려고 한다.
해당 질문을 준 팀원의 블로그도 구경가보면 좋을 것 같다.
테스트 격리성의 의미
테스트 격리성이란, 각 테스트 케이스가 다른 테스트 케이스의 결과나 상태에 영향을 받지 않고 독립적으로 실행되어야 함을 의미한다. 즉, 한 테스트의 실행이 다른 테스트의 결과에 영향을 주지 않아야 한다. 이를 통해 각 테스트가 더 예측 가능하고, 이해하기 쉽고, 신뢰할 수 있는 결과를 제공할 수 있다.
더 나아가, 테스트의 격리성을 확보한다는 것은 테스트가 서로 독립적으로 실행되어야 함을 의미한다. 이는 각 테스트 케이스가 다른 테스트 케이스의 실행 결과나 상태에 영향을 받지 않는 것을 말한다. 이를 달성하는 방법은 다양하며, Mockito의 when 메소드 사용은 그중 하나의 방법일 뿐이다.
모의 객체(Mock Object)와 격리성
모의 객체를 사용하는 주된 이유는 테스트 대상 클래스의 의존성을 격리시키기 위함이다. 예를 들어, AuthService 클래스가 다른 서비스나 데이터베이스에 의존할 경우, 이 의존성들을 모의 객체로 대체하여 실제 의존성의 복잡성이나 외부 요인의 영향 없이 AuthService의 비즈니스 로직만을 테스트할 수 있다.
when 메서드의 역할
Mockito의 when 메서드는 모의 객체가 특정 조건 하에 특정 행동을 하도록 설정하는 데 사용된다. 이를 통해 테스트 대상 메서드가 호출하는 다른 메서드들의 반환값을 예측 가능하게 만들어 준다. 하지만, 이것만으로 테스트 격리성을 완전히 확보한다고 보기는 어렵다.
격리성 확보를 위한 다른 방법
격리성을 확보하는 데에는 다른 방법도 있다.
예를 들어, 각 테스트 케이스 시작 전후로 테스트 환경을 초기화하는 작업을 통해 상태의 격리를 보장할 수 있다. 또한, 테스트 대상 클래스의 의존성을 완전히 격리시키기 위해 스프링 테스트 컨텍스트를 사용하지 않고 순수한 단위 테스트를 수행하는 방법도 있다.
테스트 격리의 또 다른 예시
@ExtendWith(MockitoExtension.class)
class AuthServiceTest {
@InjectMocks
private AuthService authService;
@Mock
private UserRepository userRepository;
@BeforeEach
void setUp() {
// 테스트 전 각 테스트 케이스에 대한 초기 설정
Mockito.reset(userRepository);
}
@Test
void testLogin() {
// given
String username = "user";
String password = "password";
when(userRepository.findByUsername(username)).thenReturn(Optional.of(new User()));
// when
authService.login(username, password);
// then
// 특정 동작에 대한 검증
}
}
위 코드에서 @BeforeEach를 사용해 각 테스트 실행 전에 userRepository를 초기화하는 것은 테스트 간 격리성을 확보하는 데 도움을 준다. 이렇게 하면 각 테스트는 서로 영향을 주지 않는 독립적인 환경에서 실행될 수 있다.
☝️ 잠깐! Mockito.reset()에 대해서 짚고 넘어가자
@BeforeEach를 사용하여 테스트 실행 전에 userRepository를 초기화한다는 것은, 각 테스트가 실행되기 전에 userRepository 모의 객체의 상태를 일관된 초기 상태로 되돌리는 것을 의미한다. 이것은 테스트 간 상호 영향을 제거하고, 각 테스트가 독립적인 환경에서 수행될 수 있도록 보장한다.
리포지토리 초기화의 중요성
상태 초기화
userRepository가 이전 테스트에서 설정한 특정 동작(예: 메서드 호출에 대한 반환값)을 기억하고 있을 수 있다. @BeforeEach에서 Mockito.reset(userRepository)를 호출함으로써, 이전 테스트에서 설정한 모든 동작을 초기화한다. 이는 각 테스트가 깨끗한 상태에서 시작되도록 해준다.
테스트 격리
테스트 간의 상호 작용을 방지함으로써, 하나의 테스트가 실패하더라도 다른 테스트에 영향을 미치지 않는다. 이는 각 테스트가 독립적으로 실행되고 그 결과가 다른 테스트에 의존하지 않음을 의미한다.
예측 가능한 결과
초기화를 통해 각 테스트가 동일한 조건에서 시작되므로, 테스트 결과가 보다 일관되고 예측 가능해진다.
reset() 메서드 설명
위 코드에서 Mockito.reset(userRepository)는 userRepository 모의 객체에 설정된 모든 조건과 행동을 제거한다.
즉, 이전 테스트에서 when()을 사용하여 설정된 행동이나 기타 설정이 모두 제거되어, 새 테스트에서는 userRepository가 마치 새롭게 생성된 것처럼 동작하게 된다.
이렇게 초기화하는 과정은 각 테스트가 서로 독립적으로 실행되고 각 테스트의 결과가 다른 테스트에 영향을 주지 않도록 하는 데 중요한 역할을 한다.
🔥 결론
결론적으로, when 메서드는 모의 객체의 행동을 제어하는 데 유용하지만, 테스트 격리성을 확보하기 위해서는 테스트 환경의 초기화, 순수한 단위 테스트의 실행, 그리고 Mockito의 다양한 기능을 적절히 조합하여 사용하는 것이 중요하다.
테스트의 격리성은 각 테스트가 독립적으로 실행될 수 있도록 보장하는 것을 의미하며, 모의 객체를 사용하여 의존성을 제어하고 테스트 대상 클래스만을 격리하여 테스트하는 것이 이를 달성하는 주요 방법 중 하나이다. 이렇게 함으로써, 각 테스트는 보다 안정적이고 신뢰할 수 있는 결과를 제공할 수 있다.
Mockito when().thenReturn()에서 파라미터로 eq()와 직접 변수 사용의 차이점 이해하기
'Spring > Spring 테스트코드' 카테고리의 다른 글
스프링 이벤트 리스너 테스트를 위한 @SpyBean과 @MockBean의 활용 (0) | 2024.01.14 |
---|---|
Mockito when().thenReturn()에서 파라미터로 eq()와 직접 변수 사용의 차이점 이해하기 (0) | 2024.01.11 |
스프링 테스트 코드에서 실제 호출 @SpyBean으로 확인하기 (3) | 2023.12.31 |
스프링 테스트 코드: @MockBean과 @Mock의 차이 (+ @InjectMocks) (1) | 2023.12.31 |
테스트 코드의 가독성 향상 - BDD 방법론과 @DisplayName 어노테이션 활용 (2) | 2023.12.26 |