(2) 테스트 코드

Yunes·2023년 5월 17일
0

Spring Boot

목록 보기
3/7

TDD vs Unit Test

이 장에서는 TDD 와 Unit Test의 차이점에 대해 소개했다.
TDD 는
1. 항상 실패하는 Unit Test 작성
2. 테스트가 통과하는 프로덕션 코드 작성
3. 트스트가 통과하면 프로덕션 코드 리팩토링
이라고 한다면

Unit Test 는 TDD 1단계인 기능 단위의 테스트 코드 작성을 의미한다. 이 장에서는 Unit Test 위주로 설명이 나와 있다.

단위 테스트의 장점

  • 예상치 못한 오류 방지
  • 수동 작업 줄이고 자동화
  • 개발자가 만든 기능 보호

테스트 코드 관련 프레임워크

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

메인 클래스

@SpringBootApplication 은 스프링 부트의 자동 설정, 스프링 Bean 읽기와 생성을 자동으로 생성한다.

WAS를 사용하는 이유

항상 서버에 톰캣을 설치할 필요가 없고 언제 어디서나 같은 환경에서 스프링 부트를 배포할 수 있게된다.

예시 컨트롤러 - HelloController

package com.springboot.book.springbootwebservice.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을 반환하는 컨트롤러로 만들어준다.

@GetMapping

  • HTTP Method 인 Get의 요청을 받을 수 있는 API를 만들어 준다.
  • 이전의 @RequestMapping 과 같다.

테스트 코드 작성

package com.springboot.book.springbootwebservice.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)
@WebMvcTest(controllers = HelloController.class)
public class HelloControllerTest {
  @Autowired
  private MockMvc mvc;
  
  @Test
  public void hello_return() throws Exception {
    String hello = "hello";

    mvc.perform(get("/hello"))
        .andExpect(status().isOk())
        .andExpect(content().string(hello));
  }
}

@RunWith(SpringRunner.class)

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

@WebMvcTest

  • 여러 스프링 테스트 어노테이션 중 Web(Sprign MVC)에 집중할 수 있는 어노테이션이다.
  • 선언시 @Controller, @ControllerAdvice 등을 사용할 수 있다.
  • 단 @Service, @Component, @Repository등은 사용할 수 없다.

@Autowired

  • 스프링이 관리하는 빈을 주입받는다.

Bean : Spring IoC(Inversion of Control) 컨테이너에 의해 인스턴스화, 조립 및 관리되는 객체
종속성 주입에 사용된다.

@Autowired
private MockMvc mvc;

이 코드는 mvc 라는 필드에 MockMvc 인스턴스를 삽입한다.

  • MovckMvc 클래스는 Spring MVC 테스트 프레임워크의 일부이며 HTTP 요청 및 응답을 시뮬레이트 하여 Spring MVC 애플리케이션을 테스트하는 방법을 제공한다.
  • 단위 테스트의 맥락에서 MockMvc를 사용하면 완전한 웹 서버를 시작하지 않고도 Spring MVC 컨트롤러의 동작을 독립적으로 테스트할 수 있다.

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 등의 상태를 검증한다. 이 코드는 200인지 아닌지를 검증한다.

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

  • mvc.perform의 결과를 검증하며 Controller에서 "hello"를 리턴하기에 이 값이 맞는지 검증한다.

오류 발생

spring boot unit test initializationError .... ControllerAdvice

원인

책의 프로젝트와 달리 spring boot 프로젝트 생성시 java 17을 사용하도록 설정했었는데 jUnit 은 4를 사용하는등 conflict 가 발생한 것 같다.
그래서 @RunWith 는 jUnit4를, @ExtendWith 는 jUnit5를 사용하는데 다음과 같이 수정하여 실행하니 테스트코드가 정상적으로 작동했다.

package com.springboot.book.springbootwebservice.web;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
//import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

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;

@ExtendWith(SpringExtension.class)
//@RunWith(SpringRunner.class)
@WebMvcTest
@ComponentScan(basePackageClasses = 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));
  }
}

실제 실행결과

롬복

  • Getter, Setter, 기본 생성자, toString 등을 어노테이션을 자동으로 생성해주는 라이브러리

    compile('org.projectlombok:lombok')

package com.springboot.book.springbootwebservice.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 이 없는 필드는 생성자에 포함되지 않는다.

오류 발생


variable not initialized in the default constructor
원인 : 롬북이 정상적으로 작동하고 있지 않다.

  • gradle 버전 확인
  • gradle 5.x 이상의 경우
  • 결과
    => 게터, 세터, constructor 를 annotation 하나만으로 자동으로 생성해준다는게 굉장히 매력적이다.

@NoArgsConstructor

  • 디폴트 생성자가 자동으로 생성

@AllArgsConstructor

  • 모든 속성들을 포함하는 생성자를 자동으로 생성

테스트 코드

package com.springboot.book.springbootwebservice.web.dto;

import org.junit.jupiter.api.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);
  }
}

assertThat

  • assertj라는 테스트 검증 라이브러리의 검증 메소드
  • 검증하고 싶은 대상을 메소드 인자로 받는다.
  • 메소드 체이닝이 지원되어 isEqualTo와 같이 메소드를 이어서 사용 가능

isEqualTo

  • assertJ의 동등 비교 메소드
  • assertThat에 있는 값과 isEqualTo의 값을 비교해서 같을 때만 성공한다.

HelloController 코드 추가

package com.springboot.book.springbootwebservice.web;

import com.springboot.book.springbootwebservice.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로 넘긴 파라미터를 가져오는 어노테이션
  • 여기서는 외부에서 name (@RequestParam("name"))이란 이름으로 넘긴 파라미터를 메소드 파라미터 name(String name)에 저장한다.

HelloControllerTest 파일 수정

package com.springboot.book.springbootwebservice.web;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
//import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

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;
import static org.hamcrest.Matchers.is;

@ExtendWith(SpringExtension.class)
//@RunWith(SpringRunner.class)
@WebMvcTest
@ComponentScan(basePackageClasses = 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 응답값을 필드별로 검증할 수 있는 메소드
  • $를 기준으로 필드명을 명시한다.
  • 여기서는 name과 amount를 검증하니 $.name, $.amount로 검증한다.

실행결과

profile
미래의 나를 만들어나가는 한 개발자의 블로그입니다.

0개의 댓글

관련 채용 정보