[2. Spring boot] 스프링 부트에서 테스트 코드를 작성하자

박현우·2021년 3월 10일
0

Spring

목록 보기
2/11
post-custom-banner

TDD와 단위 테스트

요즘 백엔드 개발자/엔지니어 기업 공고를 보면 AWS, Docker, Elastic, RESTful 등등을 경험한 사람들을 우대사항으로 많이 뽑고 있습니다. 그 중

  • 빌드/테스트/배포 자동화 경험이 있으신분

이런 우대사항을 많이 보셨을 겁니다. 이처럼 현재 테스트 코드를 작성하는 것이 필수적인 요소라고 해도 과언이 아닐 정도로 중요한 위치에 자리잡고 있습니다.

TDD란?

Test-Driven-Development, 말 그대로 테스트 주도 개발입니다. 보통의 경우 테스트를 마지막에 하는 것이 일반적인데, 테스트 케이스를 작성하고 이를 통과하는 코드를 짜서 개발을 합니다. 코드 구현을 하려면 테스트 케이스를 작성해야 하기 때문에 시간이 오래걸리지만, 이것이 반복되면서 버그도 줄어들고 요구사항 변동에도 유연하게 대처할 수 있습니다.

단위테스트란?

Unit Test라고 불리며, 구현한 모듈이나 어플리케이션 안의 개별적인 코드가 정상적으로 작동되는지 자동반복되는 테스트를 의미합니다. 다른 테스트들에 비해 가장 작은 단위의 테스트입니다. 다음과 같은 장점을 가지고 있습니다.

  • 개발 초기에 문제를 발견할 수 있다.
  • 코드 결과를 보기위해 서버를 닫았다가 다시 열 필요가 없다.
  • A 모듈의 테스트 코드가 B 모듈의 기능이 잘 작동되는 것을 보장한다.

테스트 코드를 작성하면 이와같은 효과를 기대할 수 있지만, 그만큼 단위 테스트를 잘 설계하는 것이 중요하겠죠?


테스트 코드 작성하기

main-java 에 패키지를 만듭니다. 보통 패키지명은 웹사이트 주소의 역순으로 만듭니다.

이렇게 Application 이라는 java class를 패키지 안에 만들었습니다.

package com.qweadzs;

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 읽기와 생성을 자동으로 설정 됩니다. @SBA이 있는 위치부터 설정을 읽기 때문에 항상 최상단에 위치해야합니다.

main에 run으로 인해 내장 WAS를 실행합니다. 내장 WAS는 서버에 톰캣을 설치할 필요가 없어지게되고 스프링 부트로 만들어진 Jar파일로 실행할 수 있게 해줍니다. 내장 WAS의 사용은 언제 어디서나 같은 환경에서 스프링을 배포할 수 있게 해주므로 유용하게 사용됩니다.

Controller 만들기

현재 패키지 하위에 web이라는 컨트롤러 관련 클래스들을 담는 패키지를 만들었습니다.
HelloController라는 클래스를 만들고 API를 작성하겠습니다.

package com.qweadzs.web;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController // --1.
public class HelloController {
    @GetMapping("/hello") // --2.
    public String hello(){
        return "hello";
    }
}
  1. @RestController
    • 컨트롤러를 JSON을 반환하는 컨트롤러로 만들어 줍니다.
  2. @GetMapping
    • HTTP Method인 GET의 요청을 받을 수 있는 API를 만들어 줍니다. /hello로 요청이 오면 String hello를 반환합니다.

이제 테스트 코드로 검증해 보겠습니다. test/java에 패키지를 아까와 같이 만듭니다. 일반적으로 테스트 클래스는 클래스이름 뒤에 Test를 붙입니다.

package qweadzs.web;
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 org.springframework.test.web.servlet.ResultActions;

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) // 1.
@WebMvcTest(controllers = HelloController.class) // 2.
public class HelloController {
    @Autowired // 3.
    private MockMvc mvc; // 4.

    @Test
    public void hello가_리턴된다() throws Exception{
        String hello = "hello";
        
        mvc.perform(get("/hello")) // 5.
        .andExpect(status().isOk())// 6.
        .andExpect(content().string(hello)); // 7.
    }
}
  1. @RunWith(SpringRunner.class)

    • 테스트시 JUnit에 내장된 실행자 외 다른 실행자를 실행합니다.
    • 스프링 부트 테스트와 JUnit 사이에 연결자 역할을 합니다.
  2. @WebMvcTest

    • 스프링 어노테이션 중 Web에 집중할 수 있는 어노테이션입니다.
    • @Controller, @ControllerAdvice 등을 사용할 수 있습니다.
  3. @Autowired

    • Bean을 주입받습니다.
  4. private MockMvc mvc

    • 웹 API를 테스트할 때 사용됩니다.
    • 스프링 MVC 테스트의 시작점입니다.
    • GET,POST 등에 대한 API 테스트가 가능합니다.
  5. mvc.perform(get("/hello"))

    • MockMvc를 통해 /hello 주소로 HTTP GET 요청을 합니다
    • 체이닝이 지원되 아래와 같이 여러 검증 기능을 이어서 선언할 수 있습니다.
  6. .andExpect(status().isOk())

    • mvc.perform의 결과를 검증합니다
    • HTTP Header, 200, 404, 500 등의 Status를 검증합니다.
    • 여기에선 OK(200)인지 아닌지를 검증합니다.
  7. .andExpect(content().string(hello))

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

여기서 주의할 점!

우리는 인텔리제이에서 그래들로 프로젝트를 만들었기 때문에 테스트와 빌드 자체가 그래들로 기본값 설정이 되어있습니다. 이것을 인텔리제이로 바꾸어야 테스트시 오류가 나지 않습니다.
Preferences -> Build, Excutino, Deployment -> Build Tools -> Gradle ->


이제 Application.main을 실행하면 톰캣서버가 8080포트로 실행되었다는 로그와 함께 로컬호스트로 접속이 가능합니다. http://localhost:8080/hello 접속!


LOMBOK 설치

LOMBOK이란?

LOMBOK(롬복)은 자바 개발시 거의 필수적인 라이브러리라고 할 수 있습니다. DTO 작성시 toString, get, set 등을 생산자 혹은 메소드로 많이 쓰고 있지만 실제로 이렇게 쓰다 보면 코드가 정말 길어지고 복잡해지기 마련입니다. 롬복은 이러한 작업들을 클래스 앞에 어노테이션만 쓰면 컴파일시 자동으로 생성해주어 코드의 가독성을 높이고 생산성을 높여줍니다.

  1. build.gradle로 이동합니다.
  2. dependendcies에서 compile('org.projectlombok:lombok')을 추가합니다
  3. gradle을 새로고침 합니다.
  4. 플러그인을 설치합니다.
  5. settings > Build > Compiler > Annotation Processors
  6. Enable annotation processing 체크.

주의할 점은 플러그인은 한번만 설치하면 되지만 세팅은 프로젝트마다 해야합니다.


LOMBOK을 사용하여 리팩토링하기

main/java/groupid/web 패키지에 dto 패키지를 추가하고, dto패키지 안에 HelloResponseDto를 작성하겠습니다.

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter // 1.
@RequiredArgsConstructor // 2.
public class HelloResponseDto {

    private final String name;
    private final int amount;

}
  1. @Getter
    • 선언된 모든 필드의 get 메소드를 생성해 줍니다.
  2. @RequiredArgsConstructor
    • 선언된 모든 final 필드가 포함된 생성자를 생성해 줍니다.
    • final이 없는 필드는 생성자에 포함되지 않습니다.

이 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);// 1, 2
        assertThat(dto.getAmount()).isEqualTo(amount);
    }
}
  1. assertThat
    • assertj라는 테스트 검증 라이브러리의 검증 메소드입니다.
    • 검증하고 싶은 대상을 메소드 인자로 받습니다.
    • 메소드 체이닝이 지원되어 isEqualTo와 같이 메소드를 이어서 사용할 수 있습니다.
  2. isEqualTo
    • assertj의 동등 비교 메소드입니다.
    • assertThat에 있는 값과 isEqualTo의 값을 비교해서 같을 때만 성공입니다.


보시는 바와 같이 getName이나 getAmount라는 메소드와 생성자의 선언 없이 어노테이션만으로 자동으로 생성되는 것을 확인했습니다.


다음은 HelloController에 방금만든 Dto를 적용하겠습니다.

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

name, amount는 API를 호출하는 곳에서 넘겨준 값들입니다. 추가된 API를 테스트하는 코드를 HelloControllerTest에 추가합니다.

Tip. Ctrl + E를 누르면 최근 열었던 파일을 볼 수 있습니다. 파일이 점점 많아질때 유용하게 사용할 수 있습니다.


 @Test
    public void helloDto가_리턴된다() throws Exception {
        String name = "hello";
        int amount = 1000;

        mvc.perform(
                get("/hello/dto")
                        .param("name", name) // 1.
                        .param("amount", String.valueOf(amount)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name", is(name))) // 2.
                .andExpect(jsonPath("$.amount", is(amount)));
    }
  1. param
    • API 테스트할 때 사용될 요청 파라미터를 설정합니다.
    • 단, 값은 String만 허용됩니다.
    • 그래서 숫자/날짜 등의 데이터도 등록할 때는 문자열로 변경해야 합니다.
  1. jsonPath
    • JSON 응답값을 필드별로 검증할 수 있는 메소드입니다.
    • $를 기준으로 필드명을 명시합니다.
    • 여기서는 name과 amount를 검증하니 $.name, $.amount로 검증합니다.
post-custom-banner

0개의 댓글