23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
SpringBoot 는 기존 Spring 의 복잡한 의존성 관리와 설정 부분을
개발자가 SpringBoot 에서 대신 관리해줄 수 있는 자동설정 기능을 포함하고 있어서 많이 사용하고 있는 프레임 워크 중 하나 입니다.
보통 애플리케이션 개발을 할 때 한정된 예산과 짧은 개발 기간으로(어쩔 수 없는 숙명이죠 ㅠㅠ),
비지니스 로직에 집중하기 때문에 마지막 테스트 단계에서 예상치 못한 난관에 봉착할 수 있습니다.
TDD(test-driven development) 가 좋은 개념이기는 하지만 TDD를 제대로(?) 도입하기도 여러가지 어려운 점이 있습니다.
이번 블로그에서는 SpringBoot 에서 제공하는 테스트 방법을 간략하게 소개하는 시간을 가지려고 합니다.
SpringBoot 2.2 버전 이후로 JUnit5가 기본 테스트 의존성으로 설정이 되어 있습니다.
아래는 SpringBoot 3.2 버전을 Maven 으로 구성한 프로젝트의 POM 의 일부이며, spring-boot-starter-web 이 포함되 있는 것을 볼 수 있습니다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</span></artifactId>
</dependency>기본으로 설정되기 때문에 별도로 설정할 필요는 없습니다. 위의 의존성에는 아래의 종속성이 포함되어 있습니다.
(org.junit.jupiter:junit-jupiter:5.10.2가 포함되어 있는 것을 볼 수 있습니다)
테스트방법은 여러가지가 있겠지만, 크게 두 가지로 볼 수 있겠습니다.
1. 통합 테스트
2. 단위(Slice) 테스트
Spring Boot 에서 테스트 코드에 아래와 같이 @SpringBootTest 라는 어노테이션을 작성하면,
스프링 부트의 메인 진입점을 포함한 모든 하위 Scope 의 빈(Bean)들을 ApplicationContext 에 등록해 줍니다.
따라서 모든 코드를 테스트할 수 있는 통합 테스트를 할 수 있습니다.
@SpringBootTest
class UserControllerTest {
@Autowired
MockMvc mockMvc;
@Test
void hello() throws Exception {
mockMvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect(content().string("hello"))
.andDo(print());
}
}아무래도 전체를 테스트하다보니 부담이 될 수 있습니다.
이럴 때는 @WebMvc 어노테이션으로 원하는 controller 만 테스트 할 수 있습니다.
다만, 이런 단위 테스트의 경우에는 service, repository 등과 의존성이 끊기기 때문에 해당 의존성들을 @MockBean 으로 모킹 해줘야 합니다.
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
MockMvc mockMvc;
//TODO : WebMvc 에서는 외부 의존성을 주입해 줘야 합니다
@MockBean
UserController mockUserController;
@Test
void hello() throws Exception {
mockMvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect(content().string("hello"))
.andDo(print());
}
}먼저 간단한 통합테스트를 해보도록 하겠습니다.
WebMVC 통합테스트를 할 때 서블릿 컨테이너를 목업 해서 테스트해주는 환경으로 테스트 해보겠습니다.
이 때 @AutoConfigureMockMvc와 MockMvc 를 autowired 해 줘야 합니다.
UserController.class 와 UserService.class 가 있다고 가정합니다.
1) UserController.java
package hello.springtest.user;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/hello")
public String hello(){
return "hello " + userService.getName();
}
}2) UserService.java
@Service
public class UserService {
public String getName(){
return "devocean";
}
}3) 테스트 코드
package hello.springtest.user;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
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;
@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {
@Autowired
MockMvc mockMvc;
@Test
@DisplayName("통합테스트")
void test() throws Exception {
mockMvc.perform(get("/hello"))
.andExpect(status().isOk())
.andExpect(content().string("hello devocean"))
.andDo(print());
}
}위의 코드에서는 Servlet 을 목업 해줬기 때문에 테스트 코드에서는 실제 서블릿을 테스트한 것이 아니고,
목업 된 서블릿을 테스트 했다고 보시면 됩니다.
그런데, 실제 서블릿 환경에서 테스트가 필요할 수도 있습니다.
이 경우에는 서블릿 컨테이너를 구동시키고 테스트 해야 하며 아래와 같이 webEnivironment 에 RANDOM_PORT 또는 DEFINED_PORT 를 지정해 줘야 합니다,
실제 서블릿 컨테이너이기 때문에 Rest API 로 테스트를 해 줘야 합니다.
두 가지 방법이 있는데
TestRestTemplate (동기 방식)
WebTestClient (비동기 방식)
이 있습니다.
1> TestRestTemplate 코드
package hello.springtest.user;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class UserControllerTest {
@Autowired
TestRestTemplate testRestTemplate;
@Test
@DisplayName("통합테스트")
void test() throws Exception {
String result = testRestTemplate.getForObject("/hello", String.class);
assertThat(result).isEqualTo("hello devocean");
}
}TestRestTemplate 을 의존성 주입 해주고(@Autowired) 리턴 값을 테스트 해 줍니다.
2> WebTestClient
WebTestClient 는 의존성을 pom 파일에 적어줘야 합니다. 아래 코드를 POM파일에 작성해 줍니다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>package hello.springtest.user;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class UserControllerTest {
@Autowired
WebTestClient webTestClient;
@Test
@DisplayName("통합테스트")
void test() throws Exception {
webTestClient.get().uri("/hello")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("hello devocean");
}
}지금까지 서블릿을 모킹한 테스트(MockMvc) 와 실제 서블릿을 테스트(TestRestTemplate, WebTestClient)를 알아 봤습니다.
위의 테스트는 모드 실제 Service 인 UserService 까지 테스트를 한 통합 테스트로 볼 수 있습니다.
단위 테스트를 위해서 Controller 만 테스트를 할 필요가 있을 수 있습니다. 이럴 때는 @MockBean 을 통해서 해당 의존성을 모킹해 줄 수 있습니다.
아래 코드는 service controller 의 리턴값을 "sevice mock" 으로 모킹 해 줍니다.
package hello.springtest.user;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.reactive.server.WebTestClient;
import static org.mockito.Mockito.when;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class UserControllerTest {
@Autowired
WebTestClient webTestClient;
@MockBean
UserService mockUserService;
@Test
@DisplayName("통합테스트")
void test() throws Exception {
when(mockUserService.getName()).thenReturn("service mock");
webTestClient.get().uri("/hello")
.exchange()
.expectStatus().isOk()
.expectBody(String.class).isEqualTo("hello service mock");
}
}스프링 부트에서의 테스트 방법을 간단히 살펴 봤습니다.
실제로 많은 복잡한 실무 환경에서의 테스트는 어려울 수밖에 없습니다.
단위/통합 테스트 환경을 이해하고..
테스트에 필요한 @Service, @Repository 를 모킹하는 방법에 대해서 알 수 있다면 실무 환경에서의 테스트에 도움이 될 수 있을거라 생각합니다.
감사합니다.
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.