Chapter 02 스프링 부트에서 테스트 코드를 작성하자

LeeKyoungChang·2022년 5월 13일
0
post-thumbnail

스프링 부트와 AWS로 혼자 구현하는 웹 서비스 를 공부하고 정리한 내용입니다.

 

스프링 부트에서 테스트 코드를 작성

 

📚 1. 테스트 코드 소개

대부분의 서비스 회사가 테스트 코드에 관해 요구하고 있다.
요즘들어, 코딩 테스트를 알고리즘이 아닌 프로젝트를 만들고, 단위 테스트를 필수 조건으로 두었다.
취업과 이직을 하기 위해서는 테스트 코드는 절대 빠질 수 없는 요소이다.

 

📖 A. 단위 테스트

✔️ TDD vs 단위 테스트

TDD : 테스트가 주도하는 개발
단위 테스트 : TDD의 첫 번째 단계인 기능 단위의 테스트 코드를 작성하는 것을 의미한다.

스크린샷 2022-05-13 오후 3 02 30
  • RED : 항상 실패하는 테스트를 먼저 작성
  • Green : 테스트가 통과하는 프로덕션 코드를 작성
  • Refactor : 테스트가 통과하면 프로덕션 코드를 리팩토링한다.

 

단위 테스트는 TDD와 달리 테스트 코드를 꼭 먼저 작성해야하는 것도 아니고, 리팩토링도 포함되지 않는다. 순수하게 테스트 코드만 작성하는 것을 이야기 한다.
그래서 우리는 단위 테스트 코드에 대해 배우게 된다.

 

✔️ 단위 테스트의 장점

  • 단위 테스트는 개발단계 초기에 문제를 발견하게 도와준다.
  • 단위 테스트는 개발자가 나중에 코드를 리팩토링하거나 라이브러리 업그레이드 등에서 기존 기능이 올바르게 작동하는지 확인할 수 있다(예, 회귀 테스트).
  • 단위 테스트는 기능에 대한 불확실성을 감소시킬 수 있다.
  • 단위 테스트는 시스템에 대한 실제 문서를 제공한다. 즉, 단위 테스트 자체가 문서로
    사용할 수 있다.

 

⛰ 단위 테스트 배우기 전에 진행한 개발 방식
(1) 코드를작성하고
(2) 프로그램(Tomcat)을실행한뒤
(3) Postman과같은API 테스트도구로HTTP 요청하고
(4) 요청결과를System.out.println( )으로눈으로검증합니다.
(5) 결과가다르면다시프로그램(Tomcat)을중지하고코드를수정합
니다.

  • 여기서 (2) ~ (5) 매번 코드를 수정할 때마다 반복해야 한다.

 

📖 B. 테스트 코드

테스트 코드 : 새로운 기능이 추가될 때, 기존 기능이 잘 작동되는 것을 보장해주는 것

A라는 기존 기능에 기본 기능을 비롯해 여러 경우를 모두 테스트 코드로 구현해 놓았다면 테스트 코드를 수행만 하면 문제를 조기에 찾을 수 있다.

 

✔️ 테스트 코드 작성을 도와주는 프레임워크 - xUnit

개발환경(x)에 따라 Unit 테스트를 도와주는 도구

  • JUnit - Java
  • DBUnit - DB
  • CppUnit - C++
  • NUnit - .net

➡️ 이 중에서 자바용인 JUnit을 앞으로 사용하겠다!

 

📖 C. Hello Controller 테스트 코드

package springbootawsbook.springawsbook;  
  
import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
  
@SpringBootApplication  
public class SpringawsbookApplication {  
  
   public static void main(String[] args) {  
      SpringApplication.run(SpringawsbookApplication.class, args);  
   }  
  
}
  • SpringawsbookApplication 클래스는 앞으로 만들 프로젝트의 메인 클래스
  • @SpringBootApplication으로 인해 스프링 부트의 자동 설정, 스프링 Bean 읽기와 생성을 모두 자동으로 설정된다.
    • @SpringBootApplication이 있는 위치부터 설정을 읽어가기 때문에 이 클래스는 항상 프로
      젝트의 최상단에 위치해야 한다.
  • main 메소드에서 실행하는 SpringApplication.run으로 인해 내장 WAS(Web Application Server, 웹 애플리케이션 서버)를 실행한다.
    • 내장 WAS : 별도로 외부에 WAS를 두지 않고 애플리케이션을 실행할 때 내부에서 WAS를 실행하는 것을 의미한다.

이렇게 되면 항상 서버에 톰캣(Tomcat)을 설치할 필요가 없게 되고, 스프링 부트로 만들어진 Jar 파일(실행 가능한 Java 패키징 파일)로 실행하면 된다.

 

🔔 스프링 부트에서 내장 WAS를 사용하는 이유
언제 어디서나 같은 환경에서 스프링 부트를 배포할 수 있기 때문이다.

 

💡 참고
web 패키지 : 컨트롤러와 관련된 클래스들은 모두 이 패키지에 담는다.

 

✔️ Java 클래스 생성

@RestController  
public class HelloController {  
  
    @GetMapping("/hello")  
    public String hello() {  
        return "hello";  
    }  
  
}
  • @RestController
    • 컨트롤러를 JSON을 반환하는 컨트롤러로 만들어 준다.
    • @ResponseBody를 각 메소드마다 선언했던 것을 한번에 사용할 수 있게 해준다고 생각하면 된다.
  • @GetMapping
    • HTTP Method인 Get의 요청을 받을 수 있는 API를 만들어 준다.
    • 예전에는 @RequestMapping(method=RequestMethod.GET)으로 사용되었다.

➡️ /hello로 요청이 오면 문자열 hello를 반환하는 기능을 가지게 되었다.

 

✔️ Test 클래스 생성
JUnit5에서는 @RunWith이 사라지고, @ExtendWith이 추가되었다. (생략해도 됨)

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;  
  
@WebMvcTest(controllers = HelloController.class)  
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));  
    }  
}
  • @WebMvcTest
    • 여러 스프링 데이터 어노테이션 중, Web(Spring MVC)에 집중할 수 있는 어노테이션
    • 선언할 경우 @Controller, @ControllerAdvice 등을 사용할 수 있다.
    • 단, @Service, @Component, @Repository 등은 사용할 수 없다.
    • 여기서는 컨트롤러만 사용하기 때문에 선언한다.
  • @Autowired
    • 스프링이 관리하는 빈(Bean)을 주입 받는다.
  • 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, 404, 500 등의 상태를 검증한다.
    • 여기선 OK 즉, 200인지 아닌지를 검증한다.
  • .andExpect(content( ).string(hello))
    • mvc.perform의 결과를 검증한다.
    • 응답 본문의 내용을 검증한다.
    • Controller에서 “hello”를 리턴하기 때문에 이 값이 맞는지 검증한다.

 

실행 결과
스크린샷 2022-05-13 오후 4 08 36

  • .andExpect(status().isOk()).andExpect(content().string(hello))가 모두 테스트를 통과했음을 의미한다.

 

💡 참고
브라우저로 한 번씩 검증은 하되, 테스트 코드는 꼭 따라해야 한다.
수동으로 검증하고 테스트 코드를 작성하지는 않는다.
테스트 코드로 먼저 검증 후, 정말 못 믿겠다는 생각이 들 땐 프로젝트를 실행해 확인해야 한다!

 

📚 2. 롬복

롬복 : 자바 개발자들의 필수 라이브러리

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

lombok build.gradle에 추가된다.

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

 

Enable annotation processing을 체크한다.
스크린샷 2022-05-13 오후 4 19 22

 

💡 참고

  • 롬복은 프로젝트마다 설정해야 한다.
  • 플러그인 설치는 한 번만 하면 된다.
  • build.gradle에 라이브러리를 추가 및 Enable annotation processing를 체크하는 것은 프로젝트마다 진행해야 한다.

 

📖 A. Hello Controller 코드를 롬복으로 전환하기

  • 기존 코드를 롬복으로 전환할 때, 테스트 코드가 기존 코드를 보호해주기 때문에 쉽게 롬복으로 변경할 수 있다.
  • 롬복으로 변경하고 문제가 생기는지는 테스트 코드만 돌려보면 알 수 있다.

 

HelloResponseDto

@Getter  
@RequiredArgsConstructor  
public class HelloResponseDto {  
  
    private final String name;  
    private final int amount;  
}
  • @Getter : 선언된 모든 필드의 get 메서드를 생성해준다.
  • @RequiredArgsConstructor
    • 선언된 모든 final 필드가 포함된 생성자를 생성해준다.
    • final이 없는 필드는 생성자에 포함되지 않는다.

 

HelloReponseDtoTest

package springbootawsbook.springawsbook.web.dto;  
  
import org.junit.jupiter.api.Test;  
  
import static org.assertj.core.api.Assertions.*;  
  
class HelloResponseDtoTest {  
  
    @Test  
    public void 롬복_기능_테스트() throws Exception {  
        // 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);  
    }  
  
}
  • assertThat
    • assertj라는 테스트 검증 라이브러리의 검증 메소드
    • 검증하고 싶은 대상을 메소드 인자로 받는다.
    • 메소드 체이닝이 지원되어 isEqualTo와 같이 메소드를 이어서 사용할 수 있다.
  • isEqualTo
    • assertj의 동등 비교 메소드
    • assertThat에 있는 값과 isEqualTo의 값을 비교해서 같을 때만 성공

 

실행 결과
스크린샷 2022-05-13 오후 4 36 51

 

💫 Junit과 비교하여 assertj의 장점

  • CoreMatchers와 달리 추가적으로 라이브러리가 필요하지 않다.
    • Junit의 assertThat을 쓰게 되면 is()와 같이 CoreMatchers 라이브러리가 필요하다.
  • 자동완성이 좀 더 확실하게 지원된다.
    • IDE에서는 CoreMatchers와 같은 Matcher 라이브러리의 자동완성 지원이 약하다.

 

이제 HelloController에도 새로 만든 ResponseDto를 사용하도록 코드를 추가해보자!

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를 호출하는 곳에서 넘겨준 값들이다.

 

HelloControllerTest에 추가


import static org.hamcrest.Matchers.is;

@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 응답값을 필드별로 검증할 수 있는 메소드이다.
    • $를 기준으로 필드명을 명시한다.
    • 여기서는 nameamount를 검증하니 $.name, $.amount로 검증한다.

 

실행 결과
스크린샷 2022-05-13 오후 4 56 02

 

profile
"야, (오류 만났어?) 너두 (해결) 할 수 있어"

0개의 댓글