무엇을 테스트할 것인가?가 제일 중요한 포인트
스프링 부트에서는 애플리케이션 테스트를 도와주는 다양한 유틸리티와 어노테이션을 제공한다.
테스트는 두 가지 모듈에 의해서 제공된다.
spring-boot-test
: 핵심 기능을 포함
spring-boot-test-autoconfigure
: 테스트를 위한 자동 설정을 제공
개발자들의 편의를 위해 spring-boot-starter-test
라는 스타터를 제공
spring-boot-starter-test
에서 제공되는 의존성
- JUnit 5 : The de-facto standard for unit testing Java applications
- Spring Test & Spring Boot Tets : Utilities and integration test support for Spring Boot applications
- AssertJ : A fluent assetion library
- Hamcrest : A library of matcher objects (also known as constraints or predicates)
- Mokito : A Java mocking framework
- JSONassert : An assertion library for JSON
- JsonPath : XPath for JSON
스프링 부트의 통합 테스트
@SpringBootTest
- 스프링 부트에서는 Spring Boot 기능이 필요할 때 스프링 테스트 표준 @ContextConfiguration 어노테이션의 대안으로 사용할 수 있는
@SpringBootTest
어노테이션을 제공한다.
- @ContextConfiguration : 통합 테스트를 위한 애플리케이션 컨텍스트를 어떻게 로드하고 설정하는지를 결정하는데 사용되는 클래스 레벨의 메타 데이터들을 정의
- 통합 테스트에 필요한 빈들을 하나의 문맥 단위로 관리
- 해당 어노테이션 선언 시 스프링이 실행되고 ApplicationContext를 생성하여 작동한다. 클래스를 따로 지정하지 않는다면 모든 빈을 올려서 테스트한다는 것을 의미.
- JUnit 4을 사용한다면 @RunWith(SpringRunner.class)와, JUnit 5를 사용한다면 @ExtendWith(SpringExtension.class)와 함께 사용해야 한다.
- @RunWith, @ExtendWith 모두 JUnit의 테스트 러너(Runner)를 확장하는 방법을 제공 : JUnit 은 JUnit에 내장된 러너 대신 스프링 구현체로 기능 확장 가능
- (참고) JUnit5를 사용할 때는 @SpringBootTest 어노테이션 속에@ExtendWith(SpringExtension.class)가 이미 메타 애노테이션으로 사용됐기 때문에 @SpringBootTest를 사용하는 코드에 다시 선언하지 않아도 된다.
- @SpringBootTest 어노테이션의 사용은 기본적으로 서버를 시작시키지 않는다. 이제껏 스프링을 실행시킨다는 의미에 서버를 시작시킨다는 의미가 내포된 것인 줄 알았다. Intellij의 Run을 이용해 애플리케이션을 실행시키면 서버가 실행되었으니까! 스프링을 이용해 물론 서버를 실행시키도록 서버 환경을 구축할 수는 있지만 스프링은 IoC 컨테이너에 등록된 빈들을 관리해주는 역할을 수행하고 객체들 간의 의존 관계를 만들어준다. @SpringBootTest의
webEnvironment 속성
을 사용하여 테스트 실행 방법을 세분화할 수 잇다.
- webEnvironment 속성에는 어떤 것들이 있을까?
- MOCK(Default) : web ApplicationContext를 로드하고 mock web environment(원래의 서블릿 컨테이너와는 다른 의미)를 제공한다. 내장 서버는 해당 어노테이션을 사용할 때 시작되지 않는다. 클래스 경로에서 웹 환경을 이용할 수 없는 경우, 웹이 아닌 일반 ApplicationContext를 만드는 것으로 투명하게 대체된다. 웹 애플리케이션 mock 기반 테스트를 위해 @AutoConfigureMockMvc 또는 @AutoConfigureWebTestClient와 함께 사용할 수 있다.
- RANDOM_PORT : WebServerApplicationContext를 로드하고 실제 웹 환경을 제공한다. 내장 서버가 시작되고 랜덤 포트에서 수신한다.
- DEFINED_PORT : WebServerApplicationContext를 로드하고 실제 웹 환경을 제공한다. 내장 서버가 시작되고 정의된 포트 또는 기본 포트인 8080에서 수신한다.
- NONE : 스프링 애플리케이션을 사용함으로써 ApplicaitonContext를 로드하지만 어떠한 웹 환경도 제공하지 않는다.
그러면 여기서 통합 테스트란 무엇인가?
- 단위 테스트보다 더 큰 동작을 달성하기 위해 여러 모듈들을 모아 이들이 의도대로 협력하는지 확인하는 테스트
테스트에 @Transactional 어노테이션이 있는 경우, 기본적으로 각 테스트 메서드의 끝에서 트랜잭션을 롤백시킨다. 그러나 RANDOM_PORT 또는 DEFINED_PORT와 함께 이 배치를 사용하면 실제 서블릿 환경을 제공하므로 HTTP 클라이언트와 서버가 별도의 스레드에서 실행되고 별도의 트랜잭션이 실행된다. 이 경우에는 서버에서 시작된 어떤 트랜잭션도 롤백되지 않는다.
Mock 환경에서의 테스트
@SpringBootTest는 내장 서버를 실행시키지 않는 대신 web endpoint를 테스트하기 위해 mock 환경을 설정한다. 스프링 MVC를 사용하면, MockMvc
또는 WebTestClient
를 사용하여 web endpoint를 테스트할 수 있다.
- ApplicationContext를 띄우지 않고 web layer에서 동작하는 테스트에만 집중하고 싶다면 @WebMvcTest 어노테이션을 사용
mock이란 무엇인가?
- 테스트를 수행할 때 테스트를 수행할 모듈과 연결되는 외부 서비스나 모듈을 실제 사용하는 모듈을 사용하지 않고 실제의 모듈을 흉내내는 가짜 모듈을 작성하여 테스트의 효율성을 높이는데 사용하는 객체이다.
- 실제 객체를 만들기에는 비용과 시간이 많이 들거나 의존성이 길게 걸쳐져 있어 제대로 구현하기 어려울 경우, 가짜 객체를 만들어 사용한다.
mock 객체는 언제 필요한가?
- 테스트 작성을 위한 환경 구축이 어려운 경우
- 테스트가 특정 경우나 순간에 의존적인 경우
- 테스트 시간이 오래 걸리는 경우
- 개인 PC의 성능이나 서버의 성능 문제로 오래 걸릴 수 있는 경우 시간을 단축하기 위해서도 사용한다.
mock에 대한 기본적인 분류 개념
- 테스트 더블(Test Double)
- 테스트하려는 객체와 연관된 객체를 사용하기가 어렵고 모호할 때 연관된 객체를 대신해 줄 수 있는 객체
- 테스트 더블을 사용하면서 얻는 이점은?
- 테스트 대상 코드 격리
- 테스트 속도 개선
- 예측 불가능한 실행 요소 제거
- 특수한 상황 테스트 가능
- 감춰진 정보를 확인 가능
-
더미 객체(Dummy Object)
- 인스턴스화 된 객체가 필요하지만 기능까지는 필요하지 않은 경우에 사용한다.
- Dummy Obejct의 메서드가 호출되었을 때 정상 동작은 보장하지 않는다.
-
테스트 스텁(Test Stub)
- 더미 객체가 실제 동작하는 것처럼 보이게 만들어 놓은 객체이다.
- 특정 상황을 가정해서 만들어 특정 값을 리턴해 주거나 특정 메시지를 출력해 주는 작업을 한다. (상태 검증을 위해)
- 특정 상태를 가정해서 하드코딩된 형태이기 때문에 로직에 따른 값의 변경은 테스트 할 수 없다.
-
테스트 스파이(Test Spy)
- Stub의 역할을 가지면서 호출된 내용에 대해 약간의 정보를 기록할 때 사용한다.
- 테스트 더블로 구현된 객체에 자기 자신이 호출 되었을 때 확인이 필요한 부분을 기록하도록 구현한다.
- 실제 객체처럼 동작시킬 수도 있고, 필요한 부분에 대해서는 Stub로 만들어서 동작을 지정할 수도 있다.
-
Mock 객체(Mock Obejct)
- 행위를 검증하기 위해 사용되는 객체를 지칭하며 수동으로 만들 수도 있고 프레임워크를 통해 만들 수도 있다.
- 호출에 대한 기대를 명세하고 내용에 따라 동작하도록 만들어진 객체
- Mock 객체는 테스트 더블 하위객체로 써의 좁은 의미와 테스트 더블을 포함한 넓은 의미 2가지로 사용 될 수 있다.
-
페이크 객체(Fake Object)
- 여러 상태를 대표할 수 있도록 구현된 객체로 실제 로직이 구현된 것처럼 보이게 한다.
- 실제로 DB에 접속하지는 않지만 DB에 접속해서 비교할 때와 비슷한 시나리오로 동작하도록 객체 내부에 구현 할 수 있다.
- 테스트 케이스 작성을 위해서 다른 객체들과의 의존성을 제거하기 위해 사용
- 페이크 객체를 만들 때 복잡도로 인해서 노력이 많이 들어갈 경우 적절한 수준에서 구현하거나, Mock 프레임워크를 사용
이러한 테스트 더블들을 사용하기 편리하게 구현해놓은 Mocking Framework들이 존재한다.
왜 mock 환경에서 테스트를 수행하는가?
- 일반적으로 서블릿 컨테이너를 실행하는 테스트 환경보다 빠르다
- 그러나 mocking이 Spring MVC layer에서 발생하기 때문에 더 낮은 수준의 서블릿 컨테이너 동작에 의존하는 코드는 MockMvc를 사용하여 테스트할 수 없다. 그럼 이 문제를 어떻게 해결하지?
- 실제 웹 환경을 제공하는 webEnvironment 속성을 사용하여 해결할 수 있다.
- webTestClient는 live server와 mock environment 두 곳 모두 사용 가능하다.
Mocking과 Spying Beans
테스트를 진행할 때, 때때로 애플리케이션 컨텍스트 내에 있는 특정 컴포넌트에 목을 쳐서 사용할 필요성이 있고, 스프링 부트에는 ApplicationContext 안에 있는 빈을 목을 쳐서 사용할 수 있게 도와주는 Mockito Mock에서 제공하는 @MockBean 어노테이션을 제공한다.
mock을 치는 방법
- 목을 칠 대상 객체를 선정하고 해당 객체의 행위에 기반한 테스트 예상 결과를 설정한 후, 테스트 대상 메서드를 실행시킨다.
Mockito : A Java mocking framework
단위 테스트를 도와주고 Mock이 필요한 테스트에 직관적으로 사용할 수 있도록 만들어졌다.
- Mocking
- 메서드의 행위 검증
- 테스트 스텁으로 사용
Mock과 MockBean의 차이점
Mcok은 Mock 객체를 테스트를 실행하는 동안 사용할 수 있게 하기 위해 @RunWith(MockitoJUnitRunner.class)와 함게 사용해야 한다.
@MockBean의 경우 ApplicationContext에 존재하는 빈을 MockBean으로 교체해준다. 즉 ApplicationContext를 띄워서 테스트를 진행할 경우 MockBean을 사용
슬라이스 테스트
스프링 부트의 auto-configuration 시스템은 테스트를 위해서는 너무 과할 수 있기 때문에 슬라이스 테이스를 위해 필요한 부분의 설정만을 로드할 필요가 있다. 스프링 부트의 spring-boot-test-autoconfigure
모듈은 슬라이스 테스트를 할 수 있도록 다양한 테스트 어노테이션을 제공한다.
슬라이스 테스트란?
- 각 계층을 독립적으로 테스트하기 위해 사용하는 테스트
- 각 계층을 하나의 단위로 보고 하는 단위 테스트
슬라이스 테스트를 하는 이유?
- 스프링 부트의 auto-configuration 시스템은 테스트를 위해서는 너무 과할 수 있다.
- @SpringBootTest 어노테이션을 사용해서 통합 테스트를 진행한다면 모든 빈을 로드하는데 드는 시간과 비용이 높다.
- 통합 테스트의 테스트 단위가 너무 커서 각 레이어 단위 별 테스트가 필요하다.
슬라이스 테스트를 지원하는 어노테이션들
@JsonTest
: JSON 직렬화와 역직렬화가 예상대로 동작하고 있는지를 테스트하기 위해 사용되는 어노테이션
@WebMvcTest
: 스프링 MVC 컨트롤러가 예상대로 동작하고 있는지를 테스트하기 위해 사용되는 어노테이션
@DataJpaTest
: JPA 애플리케이션을 테스트하기 위해 사용되는 어노테이션
이외에도 다양한 어노테이션들이 존재한다.
현재는 컨트롤러에 대한 슬라이스 테스트를 진행할 것이기 때문에 @WebMvcTest 에 대해서 더 알아보고자 한다.
@WebMvcTest
테스트를 위한 인프라 구축을 위해 @Controller
, @ControllerAdvice
, @JsonComponent
, Converter
, GenericConverter
, Filter
, HandlerInterceptor
, WebMvcConfigurer
, WebMvcRegistrations
, HandlerMethodArgumentResolver
등을 스캔하고 이 밖에 테스트를 하는 데 필요하지 않은 컴포넌트들은 빈으로 등록하지 않는다.
- 대게 @MockBean과 MockMvc를 사용해서 테스트를 시작한다. MockMvc는 전체 HTTP 서버를 시작할 필요 없이 빠르게 테스트할 수 있는 강력한 방법을 제공한다.
MockMvc