[1] 테스트 코드

37297 단어 Java SpringJava Spring

테스트 코드란?

TDD와 단위 테스트(Unit Test)는 다른 이야기다.
TDD는 테스트가 주도하는 개발을 이야기하며 테스트 코드를 먼저 작성하는 것부터 시작한다.

반면 단위 테스트는 TDD의 첫 번째 단계인 기능 단위의 테스트 코드를 작성하는 것을 이야기한다.
TDD와 달리 테스트 코드를 꼭 먼저 작성해야 하는 것도 아니고, 리팩토링도 포함되지 않는다.
순수하게 테스트 코드만 작성하는 것을 이야기한다.

테스트 코드를 작성해야 하는 이유

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

자바의 테스트 코드 작성 프레임워크는 JUnit이다.

Hello Controller 테스트 코드 작성하기

  1. Java 디렉토리를 마우스 오른쪽 버튼으로 클릭하여 new - package를 차례로 선택
  2. 일반적으로 패키지명은 웹 사이트 주소의 역순.
  3. 예를 들어 admin.example.com이라는 사이트라면 패키지명은 com.example.admin
  4. 패키지 이름을 프로젝트 생성시 사용했던 Group Id를 활용해 작성.
  5. 패키지 아래에 java 클래스 생성. 패키지와 마찬가지로 마우스 오른쪽 버튼 클릭 후 new - java class를 차례로 선택하면 된다.
  6. 클래스의 이름은 Application으로 한다.

클래스의 코드를 다음과 같이 작성한다.

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

방금 생성한 Application 클래스는 프로젝트의 메인 클래스.

@SpringBootApplication,으로 인해 스프링 부트의 자동 설정, 스프링 Bean 읽기와 생성을 모두 자동으로 설정됨.
특히나 @SpringBootApplication이 있는 위치부터 설정을 읽어가기 때문에 이 클래스는 항상 프로젝트의 최상단에 위치해야만 한다.

main 메소드에서 실행하는 SpringApplication.run으로 인해 내장 WAS(Web Application Server, 웹 애플리케이션 서버)를 실행한다.
내장 WAS란 별도로 외부에 WAS를 두지 않고 애플리케이션을 실행할 때 내부에서 WAS를 실행하는 것을 이야기한다.
이렇게 되면 항상 서버에 톰캣을 설치할 필요가 없게 되고, 스프링 부트로 만들어진 Jar 파일(실행 가능한 Java 패키징 파일)로 실행하면 된다.

스프링 부트에서는 내장 WAS를 사용하는 것을 권장한다.
'언제 어디서나 같은 환경에서 스프링 부트를 배포' 할 수 있기 때문이다.
외장 AWS를 쓴다고 하면 모든 서버는 WAS의 종류와 버전, 설정을 일치시켜야한다.
새로운 서버가 추가되면 모든 서버가 같은 WAS환경을 구축해야 한다.
하지만 이렇게 내장 WAS를 사용할 경우 이 문제를 모두 해결할 수 있다.

테스트를 위한 Controller 만들기

현재 패키지 하위에 web이란 패키지를 만들어 본다.
1. 위에서 만들어진 패키지를 선택 후 마우스 오른쪽 버튼으로 new - package를 선택한다.
이름은 web이라 한다.
2. 컨트롤러와 관련된 클래스들은 모두 이 패키지에 담는다.
3. 테스트를 배볼 컨트롤러를 만들어본다.
4. 마찬가지로 마우스 오른쪽 버튼으로 new - java class를 선택한다.
5. 클래스의 이름은 HelloController로 한다.

생성 후 간단한 API를 작성한다.

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을 반환하는 컨트롤러로 만들어 준다.
    예전에 사용했던 @ResponseBody를 각 메소드마다 선언했던 것을 한번에 사용할 수 있게 해줌.
  1. @GetMapping
    HTTP Mathod인 Get의 요청을 받을 수 있는 API를 만들어준다.
    예전에는 @RequestMapping(method = RequestMethod GET)으로 사용되었다.
    이를통해 /hello로 요청이 오면 문자열 hellp를 반환하는 기능을 가지게 됐다.

작성한 코드가 제대로 작동하는지 테스트를 해본다.
1. WAS를 실행하지 않고, 테스트 코드로 검증해본다.
2. src/test/java 디렉토리에 앞에서 생성했던 패키지를 그대로 다시 생성한다.
3. 그리고 테스트 코드를 작성할 클래스를 생성한다.
4. 일반적으로 테스트 클래스는 대상 클래스 이름에 Test를 붙인다.
5. 그러므로 여기서는 HelloControllerTest로 생성한다.
6. 생성된 클래스에 아래와 같은 테스트 코드를 추가한다.

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
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) // 1
@WebMvcTest(controllers = {HelloController.class}) // 2
@AutoConfigureMockMvc
public class HelloControllerTest
{

    @Autowired // 3
    private MockMvc mockMvc; // 4

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

        mockMvc.perform(get("/hello")) // 5
                .andExpect(status().isOk()) // 6
                .andExpect(content().string(hello)); // 7
    }
}
  1. @ExtendWith(SpringExtension.class)
    테스트를 진행할 때 JUnit에 내장된 실행자 외에 다른 실행자를 실행시킨다.
    여기서는 SpringExtension이라는 스프링 실행자를 사용한다.
    즉, 스프링 부트 테스트와 JUnit 사이에 연결자 역할을 한다.
  1. @WebMvcTest
    여러 스프링 테스트 어노테이션 중, Web(Spring MVC)에 집중할 수 있는 어노테이션이다.
    선언할 경우 @Controller, @controllerAdvice등을 사용할 수 있음.
    단, @Service, @Component, @Repository등은 사용할 수 없음.
    여기서는 컨트롤러만 사용하기 때문에 선언한다.
  1. @Autowired
    스프링이 관리하는 빈(Bean)을 주입받는다.
  1. private MockMvc mockMvc
    웹 API를 테스트할 때 사용한다.
    스프링 MVC 테스트의 시작점이다.
    이 클래스를 통해 HEEP GET, POST등에 대한 API 테스트를 할 수 있다.
  1. mockMvc.perform(get("/hello"))
    MockMvc를 통해 /hello 주소로 HTTP GET요청을 한다.
    체이닝이 지원되어 아래와 같이 여러 검증 기능을 이어서 선언할 수 있다.
  1. .andExpect(status().isOk())
    mockMvc.perform의 결과를 검증한다.
    HTTP Header의 Status를 검증한다.
    우리가 흔히 알고 있는 200, 404, 500 등의 상태를 검증한다.
    여기선 OK 즉, 200인지 아닌지를 검증한다.
  1. .andExpect(content().string(hello))
    mockMvc.perform의 결과를 검증한다.
    응답 본문의 내용을 검증한다.
    Controller에서 "hello"를 리턴하기 때문에 이 값이 맞는지 검증한다.

테스트 코드 실행방법은 메소드 왼쪽의 화살표를 클릭

브라우저로도 한 번씩 검증은 하되, 테스트 코드는 꼭 따라 해야 한다. 그래야만 견고한 소프트웨어를 만드는 역량이 성장할 수 있다.

추가로, 절대 수동으로검증하고 테스트 코드를 작성하진 않는다. 테스트 코드로 먼저 검증 후, 정말 못믿겠다는 생각이 들 땐 프로젝트를 실행해 확인한다.

롬복 소개 및 설치

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

프로젝트에 롬복 설치
1. build.gradle에 다음 코드를 추가한다.

dependencies {
	...
    implementation('org.projectlombok:lombok')
    annotationProcessor('org.projectlombok:lombok')
    testImplementation('org.projectlombok:lombok')
    testAnnotationProcessor('org.projectlombok:lombok')
}
  1. Refresh로 라이브러리를 내려받는다.
  2. 라이브러리를 다 받았다면 롬복 플러그인을 설치한다.
  3. 윈도우(ctrl + shift + a), 맥(command + shift + a)
  4. Action 검색 후 실행
  5. lombok 검색 후 install후 인텔리제이 재실행
  6. settings > build > compiler > annotation processors
  7. Enable annotation processing을 체크한다.

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

  1. web 패키지에 dto 패키지를 추가한다.
  2. 앞으로 모든 응답 Dto는 이 Dto 패키지에 추가한다.
  3. 이 패키지에 HelloResponseDto를 생성한다.
  4. 아래와 같이 코드를 작성한다.
package com.spring_study.springboot.web.dto;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter // 1
@RequiredArgsConstructor // 2
public class HelloResponseDto {
    private final String name;
    private final int amount;
}
  1. @Getter
    선언된 모든 필드의 get 메소드를 생성해준다.
  1. @RequiredArgsConstructor
    선언된 모든 final 필드가 포함된 생성자를 생성해준다.
    final이 없는 필드는 생성자에 포함되지 않는다.
  1. 이 Dto에 적용된 롬복이 잘 작동하는지 테스트 코드를 작성.
  2. HelloResponseDtoTest 클래스의 코드는 다음과 같이 작성.
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;

public class HelloResponseDtoTest {

    @Test
    public void 롬복_기능_테스트(){
        //  givin
        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와 같이 메소드를 이어서 사용할 수 있음.
  1. isEqualTo
    assertj의 동등 비교 메소드이다.
    assertThat에 있는 값과 isEqualTo의 값을 비교해서 같을 때만 성공.

Junit의 기본 assertThat이 아닌 assertj의 assertThat을 사용한 이유
Junit과 비교하여 assertj의 장점은 다음과 같다.

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

작성된 테스트 메소드를 실행한 후, 정상적으로 기능이 수행되는 것을 확인했다면
JHelloController에도 새로 만든 ResponseDto를 사용하도록 코드를 추가한다.

    @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에 추가한다.


import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
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.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@ExtendWith(SpringExtension.class)
@WebMvcTest(controllers = {HelloController.class})
@AutoConfigureMockMvc
public class HelloControllerTest
{

    @Autowired
    private MockMvc mockMvc;

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

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

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

        mockMvc.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로 검증한다.

추가된 api도 테스트를 실행한다.

좋은 웹페이지 즐겨찾기