데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      SpringBoot 단위/통합 테스트 환경 사례

      박제영 24.03.05
      6,209 8 0
      DEVOTEE 요약
      SpringBoot는 복잡한 Spring의 의존성 관리와 설정 부분을 대신 처리해주는 자동설정 기능을 제공해, 개발자들이 비즈니스 로직에 집중할 수 있는 프레임 워크입니다. SpringBoot 2.2 버전부터 JUnit5가 기본 테스트 의존성으로 설정되어, 통합 테스트와 단위 테스트 등을 쉽게 수행할 수 있습니다. 이 파트에서는 각 코드를 Mock으로 작성하거나 실제 서블릿환경에서 테스트 등 각 상황에 따른 SpringBoot의 다양한 테스트 방법을 소개합니다.
      DEVOTEE 추천 블로그

      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가 포함되어 있는 것을 볼 수 있습니다)

      image.png


      테스트방법은 여러가지가 있겠지만, 크게 두 가지로 볼 수 있겠습니다.

      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 로 테스트를 해 줘야 합니다.

      두 가지 방법이 있는데

      1. TestRestTemplate (동기 방식)

      2. 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 를 모킹하는 방법에 대해서 알 수 있다면 실무 환경에서의 테스트에 도움이 될 수 있을거라 생각합니다.


      감사합니다.

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      박제영 님의 최신 블로그

      더보기

      DEVOTEE 추천 블로그

      동영상 기고하기