ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [2장] 스프링 부트에서 테스트 코드를 작성하자
    Spring/스프링 부트와 AWS로 혼자 구현하는 웹 서비스 2022. 11. 12. 17:07

    📌 2.1 테스트 코드 소개

     

    • TDD와 단위테스트는 다른 것!
    • TDD는 테스트 주도 개발 & 단위 테스트는 TDD의 첫 단계인 기능 단위의 테스트 코드를 작성하는 것
    • 이번 장에서는 TDD가 아닌 단위 테스트 코드를 배운다!

    < 단위 테스트의 장점 >

    • 개발 단계 초기에 문제 발견에 도움을 줌
    • 개발자가 나중에 코드를 리팩토링하거나 라이브러리 업그레이드 등에서 기존 기능이 올바르게 동작하는지 확인 가능
    • 기능에 대한 불확실성 감소 가능
    • 새로운 기능이 추가될 때, 기존 기능이 잘 동작되는 것을 보장해줌

    JUnit: 자바용 테스트 프레임워크

    책에서는 JUnit4를 사용하라고 했지만, 2019년도 책이기 때문에 JUnit5를 적용해서 실습하도록 하겠습니다!

     


     

    package com.example.techeerteama1.springboot;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class Application {
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }
    
    • 앞으로 만들 프로젝트의 메인 클래스
    • @SpringBootApplication: 스프링부트의 자동 설정, 스프링 Bean 읽기와 생성을 모두 자동으로 설정
    • @SpringBootApplication이 있는 위치부터 설정을 읽어가기 때문에 항상 프로젝트의 최상단에 위치해야 함

     

    • SpringApplication.run: WAS(Web Application Server)실행
    • 내장 WAS: 별도로 외부에 WAS를 두지 않고 애플리케이션을 싱행할 때 내부에서 WAS를 실행하는 것
      • => 항상 서버에 톰캣을 설치할 필요가 없게 되고, 스프링 부트로 만들어진 Jar 파일로 실행하면 됨
      • 내장 WAS를 사용 => 언제 어디서나 같은 환경에서 스프링 부트 배포 가능

    package com.example.techeerteama1.springboot.web;
    
    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";
        }
    }
    
    • @RestController
      • 문자열, 객체 리턴 메서드를 가지고 있음 + 컨트롤러가 문자열과 JSON 반환 가능할 수 있게 함
      • REST 방식: 특정한  URI는 반드시 그에 상응하는 데이터 자체라는 것
    • @GetMapping
      • HTTP Method인 Get의 요청을 받을 수 있는 API를 만들어 줌
      • /hello 요청이 오면 문자열 hello 를 반환하게 됨

     

     

    package com.example.techeerteama1.springboot.web;
    
    import org.junit.jupiter.api.Test;
    import org.junit.jupiter.api.extension.ExtendWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
    import org.springframework.test.context.junit.jupiter.SpringExtension;
    import org.springframework.test.web.servlet.MockMvc;
    
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
    import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
    import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
    
    @ExtendWith(SpringExtension.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));
    
        }
    }
    

     

    책에는 @RunWith(SpringRunner.class)를 사용하고 있지만, 현재는 @ExtendWith(SpringExtension.class)로 변함

    (JUnit4 에서 JUnit5로 바뀌면서..)

    • @ExtendWith(SpringExtension.class)
      • 테스트를 진행할 때 JUnit에 내장된 실행자 외에 다른 실행자를 실행시킴
      • SpringExtension라는 스프링 실행자를 사용
      • 스프링부트 테스트와 JUnit 사이에 연결자 역할
    • @WebMvcTest
      • 여러 스프링 테스트 어노테이션 중, Web(Spring MVC)에 집중할 수 있는 어노테이션
      • @Controller, @ControllerAdvice 등을 사용 가능해짐
      • @Service, @Component, @Repository 등은 사용할 수 없음
    • @Autowired
      • 스프링이 관리하는 빈을 주입 받음
    • private MockMvc mvc
      • 웹 API를 테스트할 때 사용
      • 스프링 MVC 테스트의 시작점
      • HTTP GET, POST 등에 대한 API를 테스트 할 수 있음
    • mvc.perform(get("/hello"))
      • MockMvc를 통해 /hello 주소로 HTTP GET 요청을 함
    • .andExpect(status().isOk())
      • mvc.perform의 결과를 검증
      • HTTP Header의 status를 검증
      • 여기서는 200인지(ok인지)를 검증
    • .andExpect(content().string(hello())
      • mvc.perform의 결과를 검증
      • 응답 본문의 내용 검증
      • Controller에서 "hello"를 리턴하기 때문에 이 값이 맞는지 검증

    테스트 통과!
    어플리케이션 실행

     

     


     

    ✏️ 롬복

     

    build.gradle에 롬복 의존성 추가

    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter'
        implementation 'org.springframework.boot:spring-boot-starter-web'
        testImplementation 'org.springframework.boot:spring-boot-starter-test'
        implementation 'org.projectlombok:lombok'
    }

     

    (이 다음은 롬복 플러그인 설치인데 이미 설치되어있기 때문에 생략..)

     

    package com.example.techeerteama1.springboot.web.dto;
    
    import lombok.Getter;
    import lombok.RequiredArgsConstructor;
    
    @Getter
    @RequiredArgsConstructor
    public class HelloResponseDto {
        
        private final String name;
        private final int amount;
    }
    • @Getter
      • 선언된 모든 필드의 get 메소드를 실행
    • @RequiredArgsConstructor
      • 선언된 모든 final 필드가 포함된 생성자를 생성
      • final이 없는 필드는 생성자에 포함되지 않음

     

    < 테스트 코드 >

    package com.example.techeerteama1.springboot.web.dto;
    
    import static org.assertj.core.api.Assertions.assertThat;
    import org.junit.jupiter.api.Test;
    
    public class HelloReponseDtoTest {
    
        @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);
    
        }
    }
    

     

     

     

    테스트 코드를 실행해보니 variable name not initialized in the default constructor 이런 오류가 발생했다.

    lombok 의존성 관련 오류였고 build.gradle에서 기존 lombok관련 의존성 코드는 지우고 아래와 같이 변경해 주었다.

    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'

     

    다시 테스트를 실행하니 정상적으로 작동한다.

     

     

    <HelloController 에 아래 코드 추가>

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

    name과 amount는 API를 호출하는 곳에서 넘겨준 값들임

     

    >> 추가된 API를 테스트하는 코드 작성!

     

    @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))
                );
    }
    • param
      • API 테스트할 때 사용될 요청 파라미터를 설정
      • 값은 String만 허용
    • jsonPath
      • JSON 응답값을 필드별로 검증할 수 있는 메소드
      • $기준으로 필드명을 명시함

    테스트 통과

     

     

     

     

Designed by Tistory.