본문 바로가기
스프링 부트와 AWS

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

by danny-j 2022. 11. 11.

(단위)테스트 코드를 작성하는 이유

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

 

단위 테스트를 사용하기 전의 개발방식

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

 

여기서 2~5는 매번 코드를 수정할 때마다 반복해야함

  • 왜 계속 톰캣을 내렸다가 다시 실행하는 일을 반복할까?
  • 이는 테스트 코드가 없다 보니 눈과 손으로 직접 수정된 기능을 확인할 수 밖에 없기 때문

 

테스트 코드가 있다면?

  • 자동검증이 가능
  • 개발자가 만든 기능을 안전하게 보호
  • 기존에 잘되던 A기능에 문제가 생긴 것을 발견
  • 서비스의 모든 기능을 테스트할 수는 없음
  • 기존 기능이 잘 작동되는 것을 보장

 

메인 클래스

package com.danny.makewebalone;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MakeWebAloneApplication {

    public static void main(String[] args) {
        SpringApplication.run(MakeWebAloneApplication.class, args);
    }

}

교재에서는 메인 클래스를 직접 생성하였지만

본인은 프로젝트 생성 시에 같이 만들어져있습니다.

 

@SpringBootApplication

  • 스프링 부트의 자동 설정, 스프링 Bean읽기와 생성을 모두 자동으로 설정
  • 톰캣을 실행하지 않아도 내장 WAS로 내부에서 실행 가능

 

Hello Controller

package com.danny.makewebalone.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을 반환하는 컨트롤러로 만들어줌
  • 예전에는 @ResponseBody를 각 메소드마다 선언했던 것을 한번에 사용할수 있게 함

@GetMapping

  • HTTP Method인 Get의 요청을 받을 수 있는 API를 만들어 줌

 

Hello Controller 테스트 코드 작성하기

package com.danny.makewebalone.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 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;

@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));
    }

 

JUnit이란? 단위 테스트 도구

@RunWith(SpringRunner.class)

  • SpringRunner라는 스프링 실행자를 사용
  • 즉, 스프링 부트 테스트와 JUnit사이에 연결자 역할

@WebMvcTest(controllers = HelloController.class)

  • 여러 스프링 테스트 어노테이션 중, Web(Spring MVC)에 집중할 수 있는 어노테이션
  • 선언할 경우 @Controller, @ControllerAdvice등을 사용할 수 있음

@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, 400, 500 등의 상태를 검증
  • 여기서 OK 즉, 200인지 아닌지를 검증

.andExpect(content().string(hello));

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

 

Test코드 실행 결과

  • 버튼을 누르면 다음과 같이 정상적으로 테스트 결과가 나옴

 

만약 리턴값 검증을 Hello가 아닌 다른 값을 넣는다면?

  • Tests failed가 나오고 다르게 입력한 리턴값과 실제 리턴값을 알려줌

 

Main 메소드 실행 결과

  • 정상적으로 실행이 됨

 

롬복이란?

  • 자바 개발자들의 필수 라이브러리
  • Getter, Setter, 기본생성자, toString 등을 어노테이션으로 자동 생성

 

Hello ControllerDto

package com.danny.makewebalone.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.danny.makewebalone.web.dto;

import org.junit.Test;

import static org.assertj.core.api.Assertions.assertThat;

public class HelloResponseDtoTest {

    @Test
    public void 롬복_기능_테스트() {
        //given
        String name = "danny";
        int amount = 1000;

        //when
        HelloResponseDto dto = new HelloResponseDto(name, amount);

        //then
        assertThat(dto.getName()).isEqualTo(name);
        assertThat(dto.getAmount()).isEqualTo(amount);
    }
}

assertThat

  • assertThatj라는 테스트 검증 라이브러리의 검증 메소드
  • 검증하고 싶은 대상을 메소드 인자로 받음
  • 메소드 체이닝이 지원되어 isEqualTo와 같이 메소드를 이어서 사용할 수 있음
  • assertThat에 있는 값과 isEqualTo의 값을 비교해서 같을 때만 성공

 

HelloController에 dto컨트롤러 추가

@GetMapping("/hello/dto")
    public HelloResponseDto helloDto(@RequestParam("name") String name,
                                     @RequestParam("amount") int amount) {
        return new HelloResponseDto(name, amount);
    }

@RequestParam

  • 외부에서 API로 넘긴 파라미터를 가져오는 어노테이션
  • (@RequestParam("name")이란 이름으로 넘긴 파라미터를 메소드 파라미터 name(String name)에 저장

 

HelloControllerDto 테스트 코드 작성하기

@Test
    public void helloDto가_리턴된다() throws Exception {
        String name = "danny";
        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 응답값을 필드별로 검증할 수 있는 메소드
  • $를 기준으로 필드명을 명시
  • 여기서 name과 amount를 검증하니 $.name, $.amount로 검증

 

Test 결과

Passed

댓글