[test] Test Double과 Mockito, BDDMockito 프레임워크

2021. 12. 8. 21:53[ 백엔드 개발 ]/[ Spring ]

Test Double

테스팅을 목적으로 실제 객체 대신에 사용하는 가짜 객체를 의미하며 Dummy, Fake Object, Stub, Mock들이 있다. Test Double이 없다면 테스트할 모듈과 의존관계를 갖는 모든 객체를 직접 생성하고 세팅해야 한다. 

 

 

상태검증과 행위검증

1. 상태검증

메소드가 수행된 후 SUT나 협력 객체의 '상태를 살펴봄'으로써 올바로 동작했는지를 판단한다. 

예) 예상했던 '값'과 실제 '값'이 같은지 테스트

 

cf. SUT(System Under Test) : 테스드 대상이 되는 주요 객체

 

2. 행위검증

SUT에서 협력객체의 특정 메서드가 '호출되었지 등의 행위'를 검사함으로써 올바로 동작했는지 판단한다.

예) 특정 로직의 흐름이 적절하게 모두 수행되었는지 또는 수행되면 안 될 메소드가 수행되었는지 테스트

 

 


 

Stub & Mock

stub과 mock은 모두 테스트 더블의 일종이다. 둘 외에도 여러 가지 테스트 더블이 있지만, 그중에서도 stub과 mock을 주로 구분해서 다룬다. stub과 mock의 가장 큰 차이는 mock은 행위검증을 추구한다는 점이다. stub은 호출이 되면 미리 준비된 답변으로 응답되는 객체를 의미하며, 프로그램된 것 외에는 응답하지 않는다. 보통 기능을 구현하지 않고 해당 객체를 최소한으로 구현해둔 가짜 객체를 의미한다. 

 

Mockito 프레임워크에서 @Mock을 사용하면 모킹 된 메소드의 반환 값으로 Answers.RETURNS_DEFAULTS라는 '각 타입별 default 값'을 사용한다. 즉, Mockito 프레임워크에서는 타입별로 정의된 default 값이 존재한다는 것이고 반환 값에는 관여하지 않겠다는 의미이다. 만약 특정 값을 반환하여 테스트하길 원한다면, Mockito의 doAnswer()과 같이 stub의 형태로 바꿔야 한다.

 


 

BDDMockito 프레임워크에서 테스트하기

BDDMockito는 Mockito의 진화된 버전이며 Mockito를 상속한다. 메소드명이 given-when-then 구조에 맞게 수정된 점이 가장 큰 차이점이다.

 

어노테이션 종류(Mockito, SpringBootTest, Junit)

1) @Mock

SUT와 의존성을 갖는 객체를 모킹한다. 의존성이란 단순히 필드로 갖는 것이 아니라, 메소드의 매개변수, 지역변수 등도 포함된다.

 

2) @InjectMocks

주로 SUT에 선언하며 해당 객체를 생성할 때 @Mock으로 선언된 객체를 감지하여 자동으로 목 객체를 주입한다. 스프링 컨텍스트에서 Bean으로 등록된 객체가 @AutoWired를 통해 주입되는 것과 비슷하나, 스프링 컨텍스트를 로드하진 않는다.

 

3) @ExtendWith(MockitoExtension.class)

테스트 클래스 위에 붙여주며 이를 붙여줘야지만 테스트 시작 전에 @Mock, @InjectMocks 등의 어노테이션을 감지한다. (junit의 어노테이션이다)

 

4) @Spy

의존관계를 갖는 객체에 Mock을 했더라도 일부 메소드는 실제로 실행을 시켜 결과를 가져와야 할 경우가 있다. 즉, 하나의 객체를 선택적으로 stub 하고 싶을 때 사용하는 것이 spy이다.

 

cf. Spy 예시

https://github.com/cobiyu/MockitoSample/blob/master/src/test/java/com/cobi/testdouble/non_spring/SpyTests.java

위와 같이 테스트 클래스에 @Spy로 모킹 할 객체를 지정하고 아래와 같이 OrderRepository의 createOrder()메소드에 대한 테스트 구현

 

https://github.com/cobiyu/MockitoSample/blob/master/src/test/java/com/cobi/testdouble/non_spring/SpyTests.java

spy로 지정한 orderRepository의 createOrder()만을 stub 한 코드이다. 위와 같이 stub하지 않은 orderRepository의 메소드는 실제로 실행이 되는 구조가 된다.

 

 

5) @MockBean

@MockBean은 스프링 컨텍스트에 목 객체를 등록하는 어노테이션이다. 이때, SUT 객체는 @AutoWired를 사용하며 스프링 컨텍스트에 @MockBean으로 등록된 객체를 주입받는다. 개발자가 직접 구현한 모델 단의 테스트는 스프링 컨텍스트를 로드하지 않고 테스트할 수 있지만, Controller는 스프링 컨텍스트와 연관되어있기 때문에 Controller만을 테스트하기 어렵다.(ex. security filter chain 등) 이때 컨텍스트를 로드하고 목 객체를 컨텍스트에 로드하기 위해 @MockBean을 주로 사용한다.

 

 cf. MockMvc : 가상의 요청을 만들고 컨트롤러를 수행, 검증해주는 역할을 담당

 

org.springframework.boot.test.mock.mockito.MockBean
org.mockito.Mock

 

위와 같이 @Mock은 mockito 프레임워크의 어노테이션임을 알 수 있고, @MockBean은 스프링의 어노테이션이다. 즉, @Mock과 @MockBean이 비슷하게 보이나 객체가 로드되는 위치 자체가 다름을 알 수 있다. @MockBean은 스프링 컨텍스트에 등록되기 때문에 스프링 컨텍스트를 로드하기 위해 테스트 클래스위에 @SpringBootTest를 달아줘야한다. 매번 테스트마다 스프링 컨텍스트를 로드하는 것은 무거운 작업이 될 수 있기 때문에trade-off를 고려한 뒤 사용해야 한다.

 

+) @Mock은 @InjectMocks로 생성된 객체에만 주입될 수 있음을 기억해야 함.

 

 


 

 

필요한 bean만 컨텍스트에 로드해서 테스트하기

필요한 bean만을 컨텍스트에 올리기 위해 주로 @WebMvcTest 어노테이션을 사용한다. 여기서 어노테이션의 인자로 컨트롤러를 지정해주지 않으면 @Controller, @RestController, @ControllerAdvice 등 컨트롤러와 연관된 bean들이 로드된다. @WebMvcTest 어노테이션은 스프링부트에서 제공하기 때문에 스프링 컨텍스트를 로드한다. 위에서 설명한 @SpringBootTest와의 차이점은 '모든 bean을 컨텍스트에 올리냐/필요한 bean만 올리냐'의 차이이다. @WebMvcTest 역시 스프링 컨텍스트를 기반으로 하기 때문에 @MockBean과 함께 사용한다. 또한 MockMvc를 이용해 컨트롤러를 테스트하기 위해 MockMvc를 @AutoWired 받는다. (MockMvc를 @AutoWired로 주입받을 수 있는 이유는 스프링 테스트용 컨텍스트가 따로 로드되기 때문)

 

 


 

한 줄 요약

- 테스트할 모듈만을 단위 테스트하기 위해 Test Double을 도입

- Test Double에는 대표적으로 Stub, Mock, Spy가 있음

- 테스트의 검증 방식에는 상태검증과 행위검증이 있음

- 의존성을 갖는 객체를 '어떻게 처리'해서 단위 테스트할 것인가? -> 테스트 더블

- 테스트 더블로 처리했다면 단위 테스트를 '어떻게 검증'할 것인가? -> 행위검증, 상태검증

- Mock과 Stub의 차이 : Mock은 행위 검증에 초점을 두고 Stub은 직접 반환 값을 지정하는 형태

- Mockito 프레임워크에서 발전된 것이 BDDMockito

- Mockito에는 @Mock, @InjectMocks가 있고 @Mock으로 지정된 객체만 @InjectMocks에 주입될 수 있음

- Mockito(BDDMockito)는 스프링과 관련 없는 프레임워크지만 Spring-boot-starter-test에서 패키지로 제공

- Spy로 지정한 객체는 일부 메소드만 stub으로 처리할 수 있음. stub으로 처리하지 않으면 기존 메소드가 그대로 실행됨

- @MockBean은 스프링에서 제공하는 어노테이션으로, 스프링 컨텍스트를 로드한다는 점에서 Mockito와 다름

- 모델 단의 테스트와는 다르게, 컨트롤러 단은 스프링 컨텍스트를 로드해서 테스트해야 하기 때문에 @MockBean을 주로 사용

- @MockBean을 사용하기 위해 테스트 클래스에 @SpringBootTest어노테이션을 추가해서 스프링 컨텍스트를 띄워야 함

- @SpringBootTest는 모든 bean들을 컨텍스트에 띄운다는 단점이 있음

- 필요한 bean만을 컨텍스트에 로드하기 위해 스프링부트에서 @WebMvcTest 어노테이션을 제공함

- @WebMvcTest을 사용해서 스프링 컨텍스트에 목 객체를 띄우려면 @MockBean을 사용해야 하며 SUT객체를 띄우기 위해선 @AutoWired함

 

 

cf. 다음 글

- MockMvc를 이용해 컨트롤러 테스트하기 : https://jh-labs.tistory.com/270