Junjangsee's Blog

SpringBoot - Test(테스트)

2019-05-05

images

Test(테스트)

코드를 작성하고 해당 코드에 대해서 테스트가 필요할 때 사용하는 방법들을 공부해보겠습니다.
TDD(테스트 주도형 개발)가 중요한 만큼 테스트의 중요성이 높으니 코딩시 꼭 테스트하는 습관을 길러보도록 합시다.


테스트 환경 구성하기

새로운 패키지에 Controller, Service 클래스를 생성합니다.

SampleController.class

1
2
3
4
5
6
7
8
9
10
11
@RestController
public class SampleController {

@Autowired
private SampleService sampleService;

@GetMapping("/hello")
public String hello() {
return "hello " + sampleService.getName();
}
}

SampleService.class

1
2
3
4
5
6
@Service
public class SampleService {
public String getName() {
return "junjang";
}
}

test Service를 주입받고 hello 메소드를 구현합니다.

1
2
3
4
5
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

test 테스트를 위해선 꼭 테스트 의존성이 추가되어 있어야 합니다.
pom.xml에 추가합니다.

그리고 테스트를 하기 위해 테스트클래스를 따로 만들어주어야 합니다.


SampleControllerTest.class

1
2
3
4
5
6
7
8
9
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {

@Test
public void contextLoads() {
}

}

기본적인 테스트 클래스의 구조는 위 코드와 같습니다.


테스트를 하는 4가지 방법

1. MockMVC

MockMVC ?

  • 내장 Tomcat을 생략한 테스트입니다.
  • 서블릿을 Mocking한 것이 구동됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@AutoConfigureMockMvc
public class SampleControllerTest {

@Autowired
MockMvc mockMvc;

@Test
public void hello() throws Exception {
mockMvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect(content().string("hello junjang"))
.andDo(print());

}

test

  • @SpringBootTest에 환경을 MOCK으로 설정합니다.
  • @AutoConfigureMockMvc를 추가하여야만 MockMVC를 사용할 수 있습니다.
  • @Autowired로 MockMVC 의존성을 가져옵니다.
  • Get방식으로 hello 경로로 status 200을 출력하고 문자열을 확인하여 출력합니다.

테스트를 실행하면 테스트로그가 결과값으로 출력됩니다.


RANDOM_PORT

RANDOM_PORT ?

  • 내장 Tomcat을 포함한 테스트입니다.
  • 사용 가능한 포트를 자동으로 구동시켜줍니다.
1
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)




2. TestRestTemplate

또 다른 테스트 방법입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SampleControllerTest {

@Autowired
TestRestTemplate testRestTemplate;

@Test
public void hello() throws Exception {
String result = testRestTemplate.getForObject("/hello", String.class);
assertThat(result).isEqualTo("hello junjang");
}

}

test

  • @Autowired로 TestRestTemplate를 주입받습니다.
  • 경로와 문자열을 result 변수에 선언하고 assertThat으로 비교합니다.


@MockBean

@SpringBootTest@SpringBootApplication이 구동할 때 등록하는 모든 빈을 전부 등록한 후 테스트를 하게 됩니다.
그렇게 되면 테스트가 너무 무거워지고 효율성이 떨어지기 됩니다.
그래서 @MockBean을 사용하여 필요한 빈을 Mock의 객체로 교체하여 해당 객체만 테스트시 적용되므로 가볍고 간편하게 테스트가 가능합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SampleControllerTest {

@Autowired
TestRestTemplate testRestTemplate;

@MockBean
SampleService mockSampleService;

@Test
public void hello() throws Exception {
when(mockSampleService.getName()).thenReturn("kimjunhyeung");
String result = testRestTemplate.getForObject("/hello", String.class);
assertThat(result).isEqualTo("hello kimjunhyeung");
}

}
  • Controller에서 사용하는 Service를 MockBean으로 주입합니다.
  • when 함수를 사용하여 MockBean에 등록된 메소드를 return합니다.
  • MockBean의 객체는 Mock에 등록된 빈이기 때문에 result도 받는 값이 MockBean의 return값과 동일해야 합니다.



3. WebTestClient

  • Spring5의 WebFlux에 추가된 RestClient 중 하나로 Asynchronous(비동기식) 방식입니다.
    (기존의 RestClient는 Synchronous(동기식))
  • 사용하려면 webflux 의존성을 추가해야합니다.
  • REST방식을 테스트하기에 매우 용이합니다.
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

test webflux 의존성을 추가합니다.

test

  • @Autowired를 통해 WebTestClient를 주입받습니다.
  • 주입받은 WebTestClient로 테스트 합니다.


4. 슬라이스 테스트

레이어 별로 잘라서 원하는 부분만 테스트하고 싶을 때 사용하며 가볍게 테스트 할 수 있습니다.

  • JsonTest
    참고자료 : 스프링공식문서 - JsonTest
  • WebMVCTest
    참고자료 : 스프링공식문서 - WebMvcTest
  • @Controller, @ControllerAdvice, @JsonComponent, Converter, GenericConverter, Filter, WebMvcConfigurer, HandlerMethodArgumentResolver 만 빈 으로 등록됩니다.
  • 사용할 빈이 있다면 @MockBean으로 만들어서 다 채워줘야 함 그래야 Controller이 필요한 빈을 주입받을 수 있습니다.
  • @WebMvcTest는 MockMvc로 테스트 해야합니다.
  • WebFluxTest
  • DataJPATest
  • etc..

OutputCapture

  • 로그를 비롯해서 Console에 찍히는 모든 것을 다 캡쳐합니다.
  • 로그 메세지가 어떻게 찍히는지 테스트 해볼 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
Logger logger = LoggerFactory.getLogger(SampleController.class);

@Autowired
private SampleService sampleService;

@GetMapping("/hello")
public String hello() {
logger.info("baeksu");
System.out.println("job");
return "hello " + sampleService.getName();
}

hello 메소드에 logger, System.out.println를 선언합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Rule
public OutputCapture outputCapture = new OutputCapture();

@Autowired
WebTestClient webTestClient;

@MockBean
SampleService mockSampleService;

@Test
public void hello() throws Exception {
when(mockSampleService.getName()).thenReturn("kimjunhyeung");

webTestClient.get().uri("/hello").exchange().expectStatus().isOk()
.expectBody(String.class).isEqualTo("hello kimjunhyeung");

assertThat(outputCapture.toString())
.contains("baeksu")
.contains("job");
}
  • @Rule을 사용하여 OutputCapture 객체를 생성합니다.(public 항상 사용)
  • outputCapture 함수를 사용하여 hello 메소드에 포함된 단어를 선언합니다.


test 이상 없이 두 문자열이 테스트에 통과되고 출력된 것을 볼 수 있습니다.