[Spring&AWS][1, 2] 스프링 부트에서 테스트 코드 작성하기

kiteB·2022년 3월 9일
0

Spring-AWS

목록 보기
1/13
post-thumbnail

이 글은 책 「스프링 부트와 AWS로 혼자 구현하는 웹 서비스」를 공부하고 정리한 글입니다.


[ 인텔리제이로 스프링부트 시작하기 ]

1. 인텔리제이(IntelliJ) 소개

스프링 부트로 웹 서비스를 만들기 위한 개발 도구로 인텔리제이를 사용하도록 하겠다.

✅ 인텔리제이의 강점

  • 강력한 추천 기능 (Smart Completion)
  • 훨씬 더 다양한 리팩토링과 디버깅 기능
  • 이클립스의 깃(Git)에 비해 훨씬 높은 자유도
  • 프로젝트 시작할 때 인덱싱을 하여 파일을 비롯한 자원들에 대한 빠른 검색 속도
  • HTML, CSS, JS, XML에 대한 강력한 기능 지원 → 유료 버전
  • 자바, 스프링 부트 버전업에 맞춘 빠른 업데이트

2. 프로젝트 생성하기

💡 책에서는 스프링 이니셜라이저를 이용하여 프로젝트를 생성하지 않았지만, 평소에 스프링 이니셜라이저를 사용하여 개발을 해왔고 여러 에러를 해결하는 시간을 줄이기 위해서 그냥 스프링 이니셜라이저를 이용해서 생성하였다.


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

1. 테스트 코드 소개

  • TDD와 단위 테스트(Unit Test)는 다른 것이다!
    • TDD는 테스트가 주도하는 개발이다.
      • 항상 실패하는 테스트를 먼저 작성하고 (Red)
      • 테스트가 통과하는 프로덕션 코드를 작성하고 (Green)
      • 테스트가 통과하면 프로덕션 코드를 리팩토링한다. (Refactor)
    • 단위 테스트는 TDD의 첫 번째 단계인 기능 단위의 테스트 코드를 작성하는 것을 뜻한다.

📌 이번 게시물에서는 TDD가 아닌 테스트 코드에 대해 배운다.
테스트 코드를 먼저 배운 뒤, TDD를 배워 보는 것을 추천한다.


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

위키피디아에서 말하는 테스트 코드 작성의 이점은 다음과 같다.

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

이 책의 저자는 테스트 코드의 이점을 다음과 같이 정리하였다.

1. 빠른 피드백

  • 테스트 코드가 없으면 눈과 손으로 직접 수정된 기능을 확인해야 하기 때문에 계속 톰캣을 내렸다가 다시 실행하는 일을 반복해야만 한다.
  • 테스트 코드를 작성하면 이러한 문제가 해결되므로 톰캣을 계속 내렸다가 다시 실행할 필요가 없어진다.

2. System.out.println()을 통해 눈으로 검증해야 하는 문제

  • 테스트 코드를 작성하면 더는 사람이 눈으로 검증하지 않게 자동 검증이 가능하다. 작성된 단위 테스트를 실행하기만 하면 수동 검증은 필요없게 된다.

3. 개발자가 만든 기능을 안전하게 보호해준다.

  • 예를 들어 B라는 기능이 추가되어 테스트를 했는데 잘 되어서 오픈을 했다고 가정해보자. 그런데 실제로 오픈했더니 기존에 잘되던 A 기능에 문제가 생긴 것을 발견하게 된다. 이런 문제는 규모가 큰 서비스에서는 빈번하게 발생하는 일이다. 하나의 기능을 추가할 때마다 너무나 많은 자원이 들기 때문에 서비스의 모든 기능을 테스트할 수는 없다.
  • 이렇게 새로운 기능이 추가될 때, 기존 기능이 잘 작동하는 것을 보장해주는 것이 테스트 코드이다.

📌 테스트 코드 작성을 도와주는 프레임워크

가장 대중적인 테스트 프레임워크로는 xUnit이 있는데, 이는 개발환경x에 따라 테스트를 도와주는 도구이다. 대표적인 xUnit 프레임워크들은 다음과 같다.

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

Spring 개발을 위해 이 중에서 JUnit을 사용한다.
JUnit은 버전 5까지 나왔지만, 아직 많은 회사에서 JUnit4를 사용하기 때문에 JUnit4를 사용할 것이다.


2. HelloController 테스트 코드 작성하기

📌 SpringBootApplication

  • Application 클래스는 앞으로 만들 프로젝트의 메인 클래스가 된다.
  • @SpringBootApplication으로 인해 스프링 부트의 자동 설정, 스프링 Bean 읽기와 생성 모두 자동으로 설정된다.
  • @SpringBootApplcation이 있는 위치부터 설정을 읽어가기 때문에 이 클래스는 항상 프로젝트 최상단에 위치해야만 한다.
  • main 메소드에서 실행하는 SpringApplication.run으로 인해 내장 WAS를 실행한다.
    • 내장 WAS란 별도로 외부에 WAS를 두지 않고 애플리케이션을 실행할 때 내부에서 WAS를 실행하는 것을 말한다.
    • 이렇게 되면 항상 서버에 Tomcat을 설치할 필요가 없게 되고, 스프링 부트로 만들어진 Jar 파일(실행 가능한 Java 패키징 파일)로 실행하면 된다.

📌 참고 | 내장 WAS, 외장 WAS

  • 꼭 스프링 부트에서만 내장 WAS를 사용할 수 있는 것은 아니지만, 스프링 부트에서는 내장 WAS를 사용하는 것을 권장하고 있다. “언제 어디서나 같은 환경에서 스프링 부트를 배포”할 수 있기 때문이다.
  • 외장 WAS를 쓴다고 하면 모든 서버는 WAS의 종류와 버전, 설정을 일치시켜야만 한다. 새로운 서버가 추가되면 모든 서버가 같은 WAS 환경을 구축해야만 한다. 하지만 이렇게 내장 WAS를 사용할 경우 실수할까봐 걱정할 필요가 없고 시간이 많이 드는 문제도 해결할 수 있다.

📌 HelloController 생성

@RestController
public class HelloController {

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

→ 이제 /hello로 요청이 오면 문자열 hello를 반환하는 기능을 갖게 되었다.


📌 테스트 코드로 검증하기

@RunWith(SpringRunner.class)
@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));
    }
}
  • @RunWith(SpringRunner.class)
    • 테스트를 진행할 때 JUnit에 내장된 실행자 외에 다른 실행자를 실행시킨다.
    • 여기서는 SpringRunner라는 스프링 실행자를 사용한다.
    • 즉, 스프링 부트 테스트와 JUnit 사이에 연결자 역할을 한다.
  • @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().isOf())
    • mvc.perform의 결과를 검증한다.
    • HTTP Header의 Status를 검증한다.
    • 우리가 흔히 알고 있는 200, 404, 500 등의 상태를 검증한다.
    • 여기서는 OK 즉, 200인지 아닌지를 검증한다.
  • .andExpect(content().string(hello))
    • mvc.perfomr의 결과를 검증한다.
    • 응답 본문의 내용을 검증한다.
    • Controller에서 "hello"를 리턴하기 때문에 이 값이 맞는지 검증한다.

localhost:8080/hello로 접속한 결과


3. 롬복 소개 및 설치하기

자바 개발자들의 필수 라이브러리 Lombok!

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

build.gradle에 아래 코드를 추가해주고, lombok 플러그인을 추가해주면 롬복을 사용할 수 있게 된다.

dependencies {
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
}

4. HelloController 코드를 롬복으로 전환하기

기존 코드를 롬복으로 변경해보자!

✅ dto 생성하기

web 패키지에 dto 패키지를 추가한 뒤, HelloResponseDto를 생성하자.

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

✅ 테스트 코드 작성하기

이 Dto에 적용된 롬복이 잘 작동하는지 간단한 테스트 코드를 작성해보자.

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의 값을 비교해서 같을 때만 성공이다.

💡 참고 | Junit과 비교하여 assertj의 장점

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

테스트 실행 결과


✅ HelloController가 RequestDto 사용하도록 하기

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

@RestController
public class 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)이란 이름으로 넘긴 파라미터를 메소드 파라미터 name(=String name에서의 name)에 저장하게 된다.

nameamount는 API를 호출하는 곳에서 넘겨준 값들이다. 추가된 API를 테스트하는 코드를 HelloControllerTest에 추가한다.

@RunWith(SpringRunner.class)
@WebMvcTest(controllers = HelloController.class)
class HelloControllerTest {
    ...
    @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로 검증한다.

테스트 실행 결과

profile
🚧 https://coji.tistory.com/ 🏠

0개의 댓글