ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Spring / Spring Boot] 단위 테스트 코드 작성해보기
    BACKEND/Spring 2023. 2. 13. 23:39

    Spring Boot 프로젝트를 시작하면서 어떻게 초기 작업을 진행해야하는지 전혀 감이 안잡혀서 정리하며 프로젝트를 시작하려고 한다.

     

    이 글은 책 스프링부트와 AWS로 혼자 구현하는 웹 서비스 1장를 참고하여 작성되었습니다.

     

    이 글에서 다룰 내용은 다음과 같다.

    1. 단위 테스트 코드에 대하여

    2. 메인 메소드의 코드 톺아보기

    3. 테스트 코드 JUnit4로 작성해보기

    4. 롬복의 사용법, 그리고 롬복이란?

     

    1. 단위 테스트 코드

    이 글에서는 TDD에 관한 이야기가 아닌 기능 단위로 테스트 코드를 작성하는 단위 테스트 코드에 대해서 배울 수 있다.

    단위테스트를 작성하지 않는다면 개발 단계는 다음과 같다.

    1. 코드를 작성하고
    2. 프로그램(Tomcat)을 실행한 뒤
    3. Postman과 같은 API 테스트 도구로 HTTP 요청하고
    4. 요청 결과를 System.out.println()으로 눈으로 검증한다.
    5. 결과가 다르면 다시 프로그램(Tomcat)을 중지하고 코드를 수정한다.

    이런 과정을 코드를 수정할 때 마다 반복해야한다. 이렇게 번거로운 과정을 테스트 코드는 없애줄 수 있다.

     

    1.2 테스트 코드 작성해보기

    spring initializr로 생성한 패키지 안에 ---Application 클래스가 있을 것이다. 그 Application 안에 코드를 잠시 살펴보자.

    @SpringBootApplication으로 인해 스프링 부트의 자동 설정, 스프링 Bean 읽기와 생성을 모두 자동으로 설정된다.

     

    ❗️특히 @SpringBootApplicatio이 있는 위치부터 설정을 읽어가기 때문에 이 클래스는 항상 프로젝트의 최상단에 위치해야한다.

    이 클래스는 앞으로 만들 프로젝트의 메인 클래스가 된다.

     

    main 메소드에서 실행하는 SpringApplication.run으로 인해 내장 WAS(웹 애플리케이션 서버)를 실행한다. 

     

    🟢내장 WAS란 별도로 외부에 WAS를 두지 않고 애플리케이션을 실행할 때 내부에서 WAS를 실행하는 것을 의미한다. 이렇게 되면 항상 서버에 톰캣을 설치할 필요가 없개 되고, 스프링 부트로 만들어진 Jar파일로 실행하면 된다.

     

    Application 클래스에 대한 설명이 끝났으니, 테스트를 위한 Controller를 만들어 보자.

    이번에는 현재 패키지 하위에 web이란 패키지를 만들어보겠다.

    이제 이 패키지에 컨트롤러와 관련된 클래스들을 모두 담을 것이다.

     

    그리고 테스트해볼 컨트롤러을 만들어 보자. 이 패키지에서 새로운 클래스를 생성할건데, 그 클래스의 이름음 HelloController로 하겠다. 

    생성됐으면 간단한 API를 만들어보자.

     

    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HelloController {
        @GetMapping("/hello")
        public String hello(){
            return "hello";
        }
    }
    코드 설명:
    1. @RestController
     - 컨트롤러를 JSON을 반환하는 컨트롤러로 만들어준다.
    2. @GetMapping
    - HTTP Method인 GET의 요청을 받을 수 있는 API를 만들어 준다.

    이 코드가 제대로 작동하는지 테스트를 해보자.

    WAS를 실행하지 않고 테스트 코드로 검증해보기 위해 src/test/java 디렉토리에 있는 페키지에 web 패키지를 또 만들고, 다음과 같은 테스트 코드를 추가한다.

     

    import com.carespoon.web.HelloController;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
    import org.springframework.test.context.junit4.SpringRunner;
    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.MockMvcResultMatchers.content;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
    
    @RunWith(SpringRunner.class)
    @WebMvcTest(controllers = HelloController.class)
    public class HelloControllerTest {
        @Autowired
        private MockMvc mvc;
    
        @Test
        public void hello가_리턴된다() throws Exception{
            String hello = "hello";
    
            mvc.perform(get("/hello"))
                    .andExpect(status().isOk())
                    .andExpect(content().string(hello));
        }
    }
    코드 설명
    1. @RunWith(SpringRunner.class)
     - 테스트를 진행할 때 JUnit에 내장된 실행자 외에 다른 실행자를 실행시킨다.
     - 여기서는 SpringRunner라는 스프링 실행자를 사용
     - 즉, 스프링 부트 테스트외 JUnit 사이에 연결자 역할
    2. @WebMvcTest
     - 여러 스프링 어노테이션 중, Web(Spring MV)에 집중할 수 있는 어노테이션이다.
     - 선언할 경우 @Controller, @ControllerAdvice등을 사용할 수 있다.
     - 단, @Service, @Component, @Repository 등은 사용할 수 없다.
     - 여기서는 컨트롤러만 사용하기 때문에 사용한다.
    3. @Autowired
    - 스프링이 관리하는 빈(Bean)을 주입 받는다.
    4. private MockMvc mvc;
     - 웹 API를 테스트할 때 사용한다.
     - 스프링 MVC 테스트의 시작점이다.
     - 이 클래스를 통해 HTTP GET, POST 등에 대한 API 테스트를 할 수 있다.
    5. mvc.perform(get("/hello"))
     - MockMvc를 통해 /hello 주소로 HTTP GET 요청을 한다.
     - 체이닝이 지원되어 아래와 같이 여러 검증 기능을 이어서 선언할 수 있다.
    6. .andExpect(status.isOk())
     - mvc.perform의 결과를 검증한다.
     - HTTP Header의 Status를 검증한다.
     - 우리가 흔히 알고 있는 200, 404, 500 등의 상태를 검증 : 여기서는 200인지 아닌지를 검증 => Ok 
    7. .andExpect(content().string(hello))
    -  mvc.perform의 결과를 검증한다.
    - 응답 본문의 내용을 검증한다. : 리턴 값이 맞는지 검증

    테스트 코드를 실행해보면

    이렇게 잘 실행되는 것을 볼 수 있다.

     

    여기서 약간의 트러블 슈팅이 있었는데 책과 동일하게 작성했는데 테스트코드가 실행이 안되었다. Execution failed가 콘솔창에 떠서 gradle의 테스트코드 실행을 intelliJ IDEA로 바꿔주었다. 

     

    그 과정에서 참고한 글이다.

    https://zyngirok.com/entry/IntelliJ-Java-테스트-코드-오류-Execution-failed

     

    IntelliJ | Java | 테스트 코드 실행 시 오류 -Execution failed

    테스트 코드 실행 시 오류 발생 테스트 코드 작성을 마친 다음 실행했을 때 아래와 같은 메시지가 발생할 때가 있다. Execution failed for task ':test'. No tests found for given includes : [com.... 원인 원인은 테

    zyngirok.com

    1.3 롬복에 대하여 

    롬복은 자바 개발할 때 자주 사용하는 코드 Getter, Setter, 기본 생성자, toString등을 어노테이션으로 자동 생성해 준다. 

    Springinitializr에서 프로젝트를 생성할 때 lombok 의존성을 추가하여 생성하면 build.gradle에 추가해주지 않아도 된다. 

     

    1.3.1 Hello Controller 코드를 롬복으로 전환하기 

    먼저 web패키지에 Dto 패키지를 추가하겠다.  앞으로 모든 응답 Dto는 이 Dto 패키지에 추가한다. 이 패키지에 HelloResponseDto를 생성한다.

     

    import lombok.Getter;
    import lombok.RequiredArgsConstructor;
    
    @Getter
    @RequiredArgsConstructor
    public class HelloResponseDto {
        
        private final String name;
        private final int amount;
    }

     

    이 Dto에 적용된 롬복이 잘 작동하는지 간단한 테스트 코드를 작성해보자.

     

    import org.junit.Test;
    import static org.assertj.core.api.Assertions.assertThat;
    public class HelloResponseDtoTest{
        
        @Test
        public void 롬복_기능_테스트(){
            //given
            String name = "test";
            int amount = 1000;
            //when
            HelloResponseDto dto = new HelloResponseDto(name, amount);
            
            //then
            assertThat(dto.getName()).isEqualTo(name);
            assertThat(dto.getAmount()).isEqualTo(amount);
            
        }
    }

    테스트 코드를 실행하면 다음과 같이 성공한 것을 확인할 수 있다.

     

    그러면 앞서 작성한 HelloController에서도 ResponseDto를 사용하도록 코드를 추가해보자. 

    @GetMapping("/hello/dto")
        public HelloResponseDto helloDto(@RequestParam("name") String name, @RequestParam("amount") int amount){
            return new HelloResponseDto(name, amount);
        }
    @RequestParam
    - 외부에서 API로 넘긴 파라미터를 가져오는 어노테이션
    - 여기서는 외부에서 name이란 이름으로 넘긴 파라미터를 메소드 파라미트에 저장하게 된다.

    name과 amount는 API를 호출하는 곳에서 넘겨준 값이다. 추가된 API를 테스트하는 코드를 HelloControllerTest에 추가하자.

        @Test
        public void helloDto가_리턴된다() throws Exception{
            String name = "hello";
            int amount = 1000;
    
            mvc.perform(
                    get("/hello/dto")
                    .param("name",name)
                    .param("amount", String.valueOf(amount)))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.name", is(name)))
                    .andExpect(jsonPath("$.amount", is(amount)));
        }

    추가된 API를 한번 테스트 해보면 다음과 같이 테스트를 통과하는 것을 확인할 수 있다.

    이렇게 테스트 코드를 작성하는 법을 익혔다면 다음번에는 직접 데이터베이스를 다뤄보고 실용적인 프로젝트의 테스트 코드를 작성해보자. 

Designed by Tistory.