Mock 을 사용한 테스트
실제 객체를 만들어서 테스트하기가 어려운 경우에, 가짜 객체를 만들어서 테스트하는 기술이다.
특징
- 컨텍스트 미사용: 스프링의 애플리케이션 컨텍스트를 로드하지 않아서 더 가볍고 빠르게 작동한다.
- Mock 사용: @Mock과 @InjectMocks로 가짜 객체를 주입해서 테스트.
- 의존성 간단화: 실제 데이터베이스나 외부 API 호출 없이 동작을 흉내낼 수 있어서 성능 면에서 빠르다.
장점
- 속도가 빠름: 실제 의존성을 사용하지 않기 때문에 테스트가 아주 빠르게 끝난다. 특히, 반복적으로 자주 실행해야 하는 단위 테스트(Unit Test)에서 유리하다.
- 외부 종속성 제거: 데이터베이스, 파일 시스템, 네트워크 등 외부 환경에 의존하지 않아서, 특정 환경에 구애받지 않고 테스트를 진행할 수 있다.
- 로직에 집중: 특정 로직이나 메서드 동작에만 집중해서 테스트할 수 있기 때문에, 예를 들어 비즈니스 로직을 검증하기엔 Mock 테스트가 아주 좋다.
한계
- 실제 환경과 다를 수 있음: Mock을 사용하면 외부 시스템의 동작을 흉내 내지만, 실제 데이터베이스나 다른 외부 서비스와의 상호작용을 완벽하게 묘사하지는 못한다. 그래서 통합적인 문제를 놓칠 수 있다.
- 통합 테스트의 필요성: 애플리케이션의 전체적인 동작이 잘 되는지 확인하기 위해서는, 실제 데이터베이스나 다른 서비스와의 상호작용을 포함한 통합 테스트가 필요하다. 이때는 @SpringBootTest가 적합하다.
- 의존성 주입 문제: Mock은 객체의 행동을 내가 원하는 대로 설정할 수 있지만, 실제 애플리케이션 환경에서 빈(bean)의 라이프사이클이나 의존성 주입이 제대로 동작하는지는 알 수 없다.
- 코드가 변경될 시 위험: 프로젝트 규모가 커지고, 기획이 변경되면 기존의 결과를 통해 mocking 했던 모든 테스트들은 굉장히 위험해진다. 실제로는 바뀐 상태지만, 테스트에서는 바뀌기 전이라는 괴리 속에서 테스트가 통과되면서 실제에서 문제가 발생할 확률이 높기 때문이다.
SpringBootTest 를 사용한 테스트
SpringBootTest를 사용한 경우는 실제로 스프링 애플리케이션이 실행되듯이 모든 빈(bean)을 로드하고, 데이터베이스와 같은 외부 종속성까지 포함하여 테스트를 진행한다. 이 과정에서 애플리케이션의 전체 컨텍스트를 로드하는 데 시간이 많이 걸리기 때문에 테스트 실행 속도가 느려질 수밖에 없다.
특징
- 전체 컨텍스트 로드: 스프링 애플리케이션이 실행될 때와 같이 전체 컨텍스트를 로드하고, 모든 의존성을 설정한다.
- 실제 데이터베이스 접근: 실제로 UserRepository를 통해 데이터베이스에 접근해서 데이터를 저장하니, 데이터베이스와의 I/O 작업이 추가돼 속도가 느려진다.
- 종속성 검사: 스프링 컨텍스트 내부의 빈 간 의존성까지 모두 검사하고 로드하기 때문에 시간이 더 소요된다.
Mock vs SpringBootTest, 언제 어떤 걸 쓸까?
- Mock: 빠르게 특정 비즈니스 로직이나 메서드 단위로 동작을 테스트할 때. 외부 의존성을 제거하고 로직의 정확성만 빠르게 확인하고 싶을 때 좋다.
- SpringBootTest: 실제로 애플리케이션이 전체적으로 제대로 동작하는지 확인할 때, 특히 의존성 간의 상호작용이나 데이터베이스와의 통신을 확인하고 싶을 때는 SpringBootTest가 필요하다.
결론
Mocking은 테스트 대상에 집중할 수 있도록 해주는 강력한 도구지만, 복잡하지 않은 경우나 굳이 Mocking이 필요하지 않은 상황에서는 과도하게 사용하지 않는 것이 좋다. Mock은 빠르고 간편하지만, 모든 테스트에 적합하지는 않으며, 실제 환경을 완벽히 대체하지 못할 수도 있다.
따라서, 테스트의 목적에 맞춰 적절한 방식을 선택하는 것이 중요하다. 단위 테스트에서는 Mock을 사용해 로직에 집중하고, 통합 테스트에서는 SpringBootTest를 통해 전체 애플리케이션의 동작을 확인함으로써 균형 잡힌 테스트 전략을 세우는 것이 좋다.
참고
@SpringBootTest vs @Mock