ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 2장) 스프링 부트에서 테스트 코드를 작성하자
    Java & Spring/스프링 부트와 AWS로 혼자 구현하는 웹 서비스 2020. 6. 27. 01:24
    반응형

    테스트 코드 소개

    • TDD vs 단위 테스트
      • TDD - 테스트가 주도하는 개발 (테스트 코드를 먼저 작성하는 것부터 시작)
      • 레드 그린 사이클
        • 항상 실패하는 테스트를 먼저 작성하고(Red)
        • 테스트가 통과하는 프로덕션 코드를 작성하고(Green)
        • 테스트가 통과하면 프로덕션 코드를 리팩토링함(Refactor)
      • 단위 테스트 - TDD의 첫 번째 단계인 기능 단위의 테스트 코드를 작성하는 것
        • 따라서, 테스트 코드를 먼저 작성하지 않아도, 리팩토링을 포함하지 않아도 됨
        • 개발 단계 초기에 문제를 발견하게 도와준다.
        • 리팩토링을 하거나 라이브러리 업그레이드 등에서 기존 기능이 올바르게 작동하는지 확인할 수 있음
        • 기능에 대한 불확실성을 감소
        • 시스템에 대한 실제 문서를 제공
      • 단위 테스트 장점
        • 빠른 피드백
        • 자동검증이 가능
        • 개발자가 만든 기능을 안전하게 보호 ~> 새로운 기능이 추가될 때, 기존 기능이 잘 작동되는 것을 보장
      • xUnit - 테스트 프레임워크
        • x(개발 환경)에 따라 Unit 테스트를 도와주는 도구
        • Java - JUnit, DB - DBUnit ...
    • Hello Controller 테스트 코드 작성하기

      • 일반적으로 패키지 명은 웹 사이트 주소의 역순으로 만듬

        • ex)admin.zin0.com 사이트 -> com.zin0.admin 패키지명
      • 앞으로 만들 프로젝트의 메인 클래스가 될 Application 클래스를 만든다.

        package com.zin0.book.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이 있는 위치부터 설정을 읽어 나가기 때문에, 이 클래스는 항상 프로젝트의 최상단에 위치해야만 한다.

      • main 메소드의 SpringApplication.run으로 인해 내장 WAS를 실행

      • 내장 WAS - 언제 어디서나 같은 환경에서 스프링 부트를 배포할 수 있음

    • 컨트롤러와 관련된 것을 담을 패키지(여기서는 web)을 만든다.

    • HelloController 클래스를 만든다.

      package com.zin0.book.springboot.web;
      
      import com.zin0.book.springboot.web.dto.HelloResponseDto;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RequestParam;
      import org.springframework.web.bind.annotation.RestController;
      
      @RestController
      public class HelloController {
          @GetMapping("/hello")
          public String Hello() {
              return "hello";
          }
      }
    • @RestController

      • 컨트롤러를 JSON을 반환하는 컨트롤러로 만들어 준다.
      • @ResponseBody를 각 메소드마다 선언했던 것을 한번에 사용할 수 있게 해준다고 생각하면 편함
    • @GetMapping

      • Get 요청을 받을 수 있는 API를 만든다.
    • 테스트 코드 검증을 위해, test에 HelloControllerTest 클래스를 생성한다.

      • 검증할 것과 패키지 이름이 같아야함(실습하다가 에러를 반환하길래 패키지명에 오타가 있어서 바꿔줬더니 됐음)

      • 보통 클래스 이름은 검증할 클래스 이름에 Test를 붙임

        package com.zin0.book.springboot;
        
        import com.zin0.book.springboot.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.hamcrest.Matchers.is;
        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;
        import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
        
        @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));
            }
        }
    • @RunWith(SpringRunner.class)

      • 테스트 진행 시, JUnit에 내장된 실행자 외에 다른 실행자를 실행시킴
      • 여기서는 SpringRunner라는 스프링 실행자를 사용
      • 즉, 스프링 부트 테스트와 JUnit 사이에 연결자 역할
    • @WebMvcTest

      • 여러 스프링 테스트 어노테이션 중, Web(Spring MVC)에 집중할 수 있는 어노테이션
      • 선언할 경우 @Controller, @ControllerAdvice 등을 사용 가능
      • 단, @Service, @Component, @Repository 등 사용 불가
      • 위의 클래스는 Controller 클래스기 때문에 선언함
    • @Autowired

      • 스프링이 관리하는 빈(Bean)을 주입 받음
    • @private MockMvc mvc

      • 웹 API를 테스트할 때 사용 (Get, Post 등)
      • 스프링 MVC 테스트의 시작점
    • mvc.perform(get("hello"))

      • MockMvc를 통해 /hello 주소로 GET 요청을 보냄
      • 체이닝이 지원 ~> 따라서 여러 검증 기능을 이어서 선언 가능
    • .andExpect(status().isOk())

      • mvc.perform의 결과를 검증
      • HTTP Header의 Status를 검증 ( 200, 404, 500 등 상태)
      • 여기선 200인지 아닌지 검증
    • .andExpect(content().string(hello))

      • mvc.perform의 결과 검증 ~> 응답 본문의 내용을 검증
      • Controller에서 "hello"를 리턴하기 때문에 이 값이 맞는지 검증

    • 롬복 소개 및 설치

      • 롬복 - 자바 개발자들의 필수 라이브러리
        • Getter, Setter, 기본 생성자, toString 등을 어노테이션으로 자동 생성해준다.
        • build.gradle의 의존성 부분에 compile('org.projectlombok:lombok')을 추가
        • [ Ctrl + Shift + A ]를 통해 Plugins를 선택해주고 Marketplace에서 롬복 플러그인을 인스톨해준다. (인스톨 후 재시작 필수) ~> 알림창 Enable 클릭 or "Setting -> Build -> Compiler -> Annotation Processors"에서 Enable 설정
    • Hello Controller 코드를 롬복으로 전환하기

      • 테스트 코드가 작성한 코드를 지켜주기 때문에, 쉽게 변경할 수 있다.

      • web 패키지에 모든 응답 Dto를 담을 dto 패키지를 추가

      • HelloResponseDto 클래스를 생성

        package com.zin0.book.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이 없는 필드는 생성자에 포함 X
      • HelloResponseDtoTest 클래스 생성

        package com.zin0.book.springboot.dto;
        
        import com.zin0.book.springboot.web.dto.HelloResponseDto;
        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);
            }
        }
      • aseertThat

        • assertj라는 테스트 검증 라이브러리의 검증 메소드
        • 검증하고 싶은 대상을 메소드 인자로 받음
        • 메소드 체이닝 지원
      • Junit의 기본 assertThat이 아닌 assertj의 assertThat을 사용한 이유

        • Junit의 assertThat을 쓰게 되면, is()와 같이 CoreMatchers 라이브러리가 필요하다.
        • 하지만, Junit은 추가적 라이브러리가 필요 X
        • assertj는 Junit에서 자동으로 라이브러리 등록을 해줌
    • HelloController에 ResponseDto 사용하도록 추가

      package com.zin0.book.springboot.web;
      
      import com.zin0.book.springboot.web.dto.HelloResponseDto;
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.RequestParam;
      import org.springframework.web.bind.annotation.RestController;
      
      @RestController
      public class HelloController {
          @GetMapping("/hello")
          public String Hello() {
              return "hello";
          }
      
          @GetMapping("/hello/dto")
          public HelloResponseDto helloDto(@RequestParam("name") String name, @RequestParam("amount") int amount) {
              return new HelloResponseDto(name, amount);
          }
      }
    • @RequestParam

      • 외부에서 API로 넘긴 파라미터를 가져오는 어노테이션
    • HelloControllerTest에 테스트 코드 작성

      package com.zin0.book.springboot;
      
      import com.zin0.book.springboot.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.hamcrest.Matchers.is;
      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;
      import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
      
      @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));
          }
      
          @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 응답값을 필드별로 검증
      • $를 기준으로 필드명 명시 해줘야함

    추가)

    main의 Application 클래스를 직접 확인하는 과정에서, Error를 반환

    (8080포트가 이미 점유되었다고 반환)

    1. cmd를 통해서 port를 죽이려고 시도했지만 실패했다.

    2. /f를 써서 강제로 지우려고 했지만, 어떤 프로세스의 자식 프로세스라서 종료할 수 없다고 나왔다.

    3. 그래서, 연결 포트를 바꿔주기로 결정했다.
      -> resources에 있는 applications.properties에
      server.port = 원하는 포트번호
      를 추가해서 연결 포트를 바꿔주었다.
      (applications.properties가 없어서 만들어줬음)

    그리고 신기한 점은 테스트 클래스에서 메소드를 만들 때, 한글로 적어도 지원이 된다는 것이었다..!!!

    반응형

    댓글

Designed by Tistory.