ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [09] Test
    Spring/Spring boot 2020. 6. 10. 19:18
    반응형

    [Test를 하는 이유]

    개발에 관심이 있는 분들은 TDD(Test Driven Development)를 들어보셨을 겁니다. TDD는 우선 code에 대한 Test를 작성 후 Test를 수행함에 따라 발생한 오류의 수정과 중복된 code를 제거하는 refactoring을 진행해 유지보수가 용이한 simple한 code를 얻기 위한 개발 방법입니다. 완벽한 TDD를 도입하지 않더라도 Test code를 추가하면 유지보수에 따른 오류를 미연에 방지할 수 있으며 개발자는 자신의 code에 대한 확신을 가질 수 있게 됩니다. Spring boot는 test를 위한 의존성 하나만 추가하면 test에 필요한 여러 도구들을 사용할 수 있게 되어 매우 편리합니다.

     

     

     


    [Test관련 의존성 추가]

     

    Test를 위해 project에 spring-boot-starter-test 의존성을 test scope로 추가합니다.

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

     

     

     

     


    [Test 실습을 위한 예제 code 작성]

    1. main class는 다음과 같습니다.

    @SpringBootApplication
    public class Application {
    
        public static void main(String[] args) {
    
            SpringApplication app = new SpringApplication(Application.class);
            app.setWebApplicationType(WebApplicationType.SERVLET);
            app.run(args);
        }
    }

     

    2. String type의 data를 return하는 책임을 가진 객체(bean)를 추가합니다.

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

     

    3. ~/hello 로 접근 시 response하는 RestController를 추가합니다.

    @RestController	//	①
    public class SampleController {
    
        @Autowired
        private SampleService sampleService;
    
        @GetMapping("/hello")	//	②
        public String hello() {
            return "hello " + sampleService.getName();
        }
    }
    

    ① SampleController type의 bean을 restful한 요청을 처리할 수 있는 bean으로 만들어 줍니다.

    ② hello() method를 ~/hello URI로 들어오는 get request에 대해 response하는 method로 만들어 줍니다.

     

     

     


    [Integration Test (통합 Test)]

    Application 전체적으로 객체 간의 상호작용이 잘 이뤄지는지 확인하는 test입니다.

    Test를 위한 기본적인 code는 다음과 같습니다.

    @RunWith(SpringRunner.class)	//	①
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)	//	②
    public class SampleControllerTest {
    
    
    }
    

    ① Test를 실행할 때 실행자로 SpringRunner.class를 사용하도록 합니다.

    ② @SpringBootTest annotation을 사용하여 test환경을 설정합니다. annotation의 webEnvironment 매개 값에 따라 test 환경은 다음과 같이 변경됩니다.

    • SpringBootTest.WebEnvironment.MOCK : test 수행에 Embedded web application server 대신 mocking한 servlet을 사용합니다. mocking servlet에 requeset를 넘기는 test를 수행하기 위해서는 MockMvc client를 사용해야 합니다.
    • SpringBootTest.WebEnvironment.RANDOM_PORT 또는 DEFINED_PORT : Embedded web application server를 사용하여 test합니다.
    • SpringBootTest.WebEnvironment.NONE : Servlet 환경을 제공하지 않습니다.

    환경설정이 완료되면 main의 @SpringBootApplication을 찾아서 모든 bean을 scan하여 등록한 뒤 만약 mocking된 bean이 있다면 그 bean만 교체하여 application을 실행합니다.

     

     

    MockMvc(Spring boot test의 WebEnvironment로 MOCK을 지정)를 사용하는 예

    @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 dave"))
                .andDo(print())	//	⑦
            );
        }
    }

    ① MockMvc의 자동 설정을 활성화하고 설정하기 위해 test class에 사용하는 annotation입니다.

    test에 사용할 McokMvc type bean을 주입받습니다.

    get request에 대한 error 발생에 대비하여 예외를 떠넘깁니다. 

    ④ MocMvc type bean의 perform() method를 사용하여 test를 위한 request를 던집니다.

    ⑤ /hello URI로 get request를 받습니다.

    resultmatcher의 status() method를 통해 확인한 response 결과가 200일 것을 기대합니다.

    ⑦ Test 진행에 따른 Request, Handler, Async, Exception, ModelAndView, FlashMap, Response 전체를 실행창에 출력합니다. print() method로 확인할 수 있는 대부분의 항목들을 assertion할 수 있습니다.

     

     

     

    Spring boot test의 WebEnvironment로 RANDOM_PORT를 사용하는 예

    RANDOM_PORT로 WebEnvironment를 지정한 경우에는 MockMvc 대신 TestRestTemplate이나 WebTestClient를 사용해야 합니다.

    다음은 TestRestTemplate를 사용한 test의 예입니다.

    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 helloTest() throws Exception {
            when(mockSampleService.getName()).thenReturn("dave");   // ②
    
            String result = testRestTemplate.getForObject("/hello", String.class);  //	③
            assertThat(result).isEqualTo("hello dave");
        }
    }
    

    ① application의 bean 중 이 annotaion이 적용된 bean과 동일한 type의 bean을 annotation이 적용된 bean으로 교체합니다.

    ② SampleService type의 bean을 Mocking하여 임의의 원하는 return value를 얻습니다.

    ③ request를 요청할 URI와 body에 해당하는 type을 지정합니다.

     

     

     

     

    WebTestClient를 사용한 test는 다음과 같이 진행합니다.

    import static org.mockito.Mockito.when;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    public class SampleControllerTest {
    
        @Autowired
        WebTestClient webTestClient;    //	①
    
        @MockBean   //	②
        SampleService mockSampleService;
    
        @Test
        public void helloTest() throws Exception {
            when(mockSampleService.getName()).thenReturn("dave");   //	③
    
            webTestClient.get().uri("/hello").exchange()    //request를 보내고
                    .expectStatus().isOk()  //response 상태를 점검하고
                    .expectBody(String.class).isEqualTo("hello dave");  //실제로 맞는 값인지까지 확인합니다.
        }
    }
    

    ① SpringMvcWebFlux에 추가된 RestClient 중 하나로 asynchronous하게 동작하여 request에 대한 response가 도착할 때 callback을 실행할 수 있습니다. 비동기 연결방식으로 동작하기 때문에 test code에서도 WebClient와 동일한 API를 사용할 수 있습니다. WebTestClient를 사용하려면 project에 spring-boot-starter-webflux 의존성을 추가해야 합니다.

    ② application의 bean 중 이 annotaion이 적용된 bean과 동일한 type의 bean을 annotation이 적용된 bean으로 교체합니다.

    ③ SampleService type의 bean을 Mocking하여 임의의 원하는 return value를 얻습니다.

     

     

     


    [Slice Test]

    특정 계층에 대해서만 가볍게 진행할 수 있는 test입니다. Slice test를 진행하면 특정 계층의 bean만 생성하기 때문에 의존성이 끊어져 다른 계층의 bean을 사용할 수 없게 됩니다. Test에 꼭 필요한 타계층 bean이 있는 경우 @MockBean을 사용해 가짜 bean을 만들어줘야 합니다.

     

    @JsonTest : 

    • 오직 JSON serialization에만 초점을 맞춘 JSON test를 위한 annotation입니다.
    • 이 annotation을 사용하면 full auto-configuration 대신 @JsonComponent와 Jackson Module 같이 JSON test에 관련된 configuration만 적용합니다.
    • 이 annotation이 달린 test는 JacksonTester, JsonbTester 및 GsonTester 필드도 초기화합니다.
    • @AutoConfigureJsonTesters annotation을 통해 JSON test에 대한 보다 세밀한 제어를 할 수 있습니다.
    • 이 annotation은 JUnit4를 사용하는 경우 @RunWith(SpringRunner.class)와 함께 사용해야 합니다.

     

     

    @WebMvcTest :

    • Spring MVC component에 초점을 맞춰 Spring MVC test에 사용되는 annotation입니다.
    • 이 annotation을 사용하면 전체 auto-configuration을 실행하지 않는 대신 MVC test와 관련된 configuration만 실행합니다. 예를 들어 @Controller, @ControllerAdvice, @JsonComponent, Converter / GenericConverter, Filter, WebMvcConfigurer, HandlerMethodArgumentResolver bean을 생성하고 적재합니다. 하지만 @Component, @Service, @Repository bean은 생성되지 않습니다.
    • @WebMvcTest annotation을 지정한 test는 Spring Security와 MockMvc(HtmlUnit WebClient, Selenium WebDriver 지원 포함) 역시 자동으로 구성합니다.
    • MockMvc를 좀 더 세밀하게 제어하기 원하는 경우 @AutoConfigureMockMvc annotation을 사용합니다.
    • 사용자의 @Controller bean이 필요로 하는 다른 bean들과의 협력관계를 구성하기 위해 @WebMvcTest는 보통 @MockBean, @Import와 함께 사용됩니다.
    • Slice test가 아닌 통합 test인 경우는 @WebMvcTest 보다는 @SpringBootTest를 사용할 것을 추천합니다.
    • 이 annotation은 JUnit4를 사용하는 경우 @RunWith(SpringRunner.class)와 함께 사용해야 합니다.

     

     

    @WebFluxTest :

    • Spring WebFluex component에 초점을 맞춰 Spring WebFlux test에 사용되는 annotation입니다.
    • 이 annotation을 사용하면 전체 auto-configuration을 실행하지 않는 대신 WebFlux와 연관된 configuration만 실행합니다. 예를 들어 @Controller, @ControllerAdvice, @JsonComponent, Converter/GenericConverter, WebFluxConfigurer bean을 생성하고 적재합니다. 하지만 @Component, @Service, @Repository bean은 생성되지 않습니다.

     

     

    @DataJpaTest :

    • JPA component에 초점을 둔 JPA test annotation입니다.  
    • 이 annotation을 사용하면 전체 auto-configuration을 실행하지 않는 대신 JPA test와 연관된 configuration만 실행합니다.
    • 기본적으로 @DataJpaTest annotation이 달린 test는 transactional하고 각 test가 끝날 때 rollback 됩니다.
    • 이 annotation 적용 시 in-memory database를 사용합니다. @AutoConfigureTestDatabase annotation을 사용하여 사용할 database에 대한 설정을 할 수 있습니다. 만약 application 전체를 load하면서 내장된 database를 사용하고자 하는 경우 @AutoConfigureTestDatabase와 @SpringBootTest를 같이 쓰는 것을 고려해야 합니다.
    • Junit4를 사용하는 경우 @DataJpaTest는 @RunWith(SpringRunner.class)와 함께 사용되어야 합니다.

     

     

    위의 slice test 중 @WebMvcTest에 대한 예를 살펴보면 다음과 같습니다.

    import static org.mockito.Mockito.when;
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
    
    @RunWith(SpringRunner.class)
    @WebMvcTest(SampleController.class)	//	①
    public class SampleControllerTest {
    
        @Autowired
        MockMvc mockMvc;
    
        @MockBean
        SampleService mockSampleService;
    
        @Test
        public void helloTest() throws Exception {
            when(mockSampleService.getName()).thenReturn("dave");   //	②
    
            mockMvc.perform(get("/hello"))
                .andExpect(content().string("hello dave"));
    
        }
    }
    

    ① web mvc test를 위해 @WebMvcTest annotation을 test class에 붙여줍니다. 매개값으로는 test할 bean에 해당하는 class들을 지정합니다.

    ② SampleService type의 bean을 Mocking하여 임의의 원하는 return value를 얻습니다.

     

     

     


    [Test를 위한 가짜 객체(bean)]

    Project 일부에 대해서만 test 진행할 필요가 있거나, test 진행에 필요하지만 아직 만들어지지 않은 객체를 무시하고 test를 해야 할 때 가짜 객체를 사용할 수 있습니다.

    @MockBean
    UsedBeanForTest mockBeanForTest;	//	①
    
    @Test
    public test() {
    	when(mockBeanForTest.xxx()).thenReturn("yyy");	//	②
        
        ...
    }

    ① test 수행에 필요한 type의 bean을 mocking합니다. mocking된 bean은 test마다 reset됩니다.

    ② mocking한 bean의 xxx() method가 호출되면 "yyy"를 return하도록 mockup합니다.

     

    'Spring > Spring boot' 카테고리의 다른 글

    [11] Spring Web MVC: HttpMessageConverters  (0) 2020.06.17
    [10] Spring Web MVC: Introduction  (0) 2020.06.15
    [08] Spring boot logging  (0) 2020.06.05
    [07] Profile  (0) 2020.06.03
    [06] 외부 설정 file (Property)  (0) 2020.06.01

    댓글

Designed by Tistory.