Back-end/Spring

SpringBoot - Test(테스트)

Tigger 2020. 6. 27. 23:11

Test(테스트)

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

테스트 환경 구성하기

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

SampleController.class

@RestController
public class SampleController {

    @Autowired
    private SampleService sampleService;

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

SampleService.class

@Service
public class SampleService {
    public String getName() {
        return "junjang";
    }
}

 

test

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

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

 

test

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

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

SampleControllerTest.class

@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {

    @Test
    public void contextLoads() {
    }

}

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

테스트를 하는 4가지 방법

1. MockMVC

MockMVC ?

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

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을 포함한 테스트입니다.
  • 사용 가능한 포트를 자동으로 구동시켜줍니다.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)

2. TestRestTemplate

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

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의 객체로 교체하여 해당 객체만 테스트시 적용되므로 가볍고 간편하게 테스트가 가능합니다.

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방식을 테스트하기에 매우 용이합니다.
<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에 찍히는 모든 것을 다 캡쳐합니다.
  • 로그 메세지가 어떻게 찍히는지 테스트 해볼 수 있습니다.

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를 선언합니다.

@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

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