'스프링 부트와 AWS로 혼자 구현하는 웹 서비스'를 정리한 글입니다.
테스트 코드
- Red: 항상 실패하는 테스트를 먼저 작성하고
- Green: 테스트가 통과하는 프로덕션 코드를 작성하고
- Refactor: 테스트가 통과하면 프로덕션 코드를 리팩토링합니다.
TDD: 테스트가 주도하는 개발로 먼저 테스트 코드를 작성하는 것부터 시작한다.
단위 테스트: TDD의 첫 번째 단계인 기능 단위의 테스트 코드를 작성하는 것.
이번 장에서 단위 테스트 코드를 배운다.
단위 테스트 코드를 작성함으로써 얻는 이점
- 개발단계 초기에 문제를 발견하게 도와준다.
- 개발자가 코드를 리팩토링하거나 라이브러리 업그레이드 등에서 기존 기능이 올바르게 작동하는지 확인할 수 있다.
- 기능에 대한 불확실성을 감소시킬 수 있다.
- 시스템에 대한 실제 문서를 제공한다. => 단위 테스트 자체가 문서로 사용할 수 있다.
- 빠른 피드백
- 테스트 코드를 작성하면 자동 검증이 가능하다.
- 개발자가 만든 기능을 안정하게 보호해준다.
JUnit5로 테스트 코드를 작성해보자.
Hello Controller 테스트 코드 작성하기
패키지 생성 => 클래스 생성 => 클래스 코드 작성
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를 실행한다.
항상 서버에 톰캣을 설치할 필요가 없게 되고, Jar 파일로 실행하면 된다.
스프링 부트에서는 내장 WAS를 사용하는 것을 권장하고 있다.
그 이유는 '언제 어디서나 같은 환경에서 스프링 부트를 배포'할 수 있기 때문이다.
Controller 만들기
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";
}
}
WAS를 실행하지 않고, 테스트 코드로 검증한다.
src/test/java 디렉터리에 패키지 생성 => 클래스 생성 (테스트 클래스는 대상 클래스 이름에 Test를 붙인다.)
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.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)
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));
}
}
@Extendwith(SpringExtension.class)
- 스프링 부트 테스트와 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().isOk())
- mvc.perform의 결과를 검증한다.
- HTTP header의 Status를 검증한다.
- 흔히 알고 있는 200, 404, 500 등의 상태를 검증한다.
- 여기서 200인지 아닌지를 검증한다.
.andExpect(content().string(hello))
- mvc.perform의 결과를 검증한다.
- 응답 본문의 내용을 검증한다.
- Controller에서 "hello"를 리턴하기 때문에 이 값이 맞는지 검증한다.
Hello Controller 코드를 롬복으로 전환하기
롬복은 자주 사용하는 코드 Getter, Setter, Constructor, toString 등을 어노테이션으로 자동 생성해 준다.
web 패키지에 dto 패키지(모든 응답 Dto는 이 Dto 패키지에 추가)를 추가 => 클래스 생성
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter // 선언된 모든 필드의 get 메서드를 생성해 준다.
@RequiredArgsConstructor // 선언된 모든 final 필드가 포함된 생성자를 생성해 준다.
public class HelloResponseDto {
private final String name;
private final int amount;
}
@Getter
- 선언된 모든 필드의 get 메서드를 생성해 준다.
@RequiredArgsConstructor
- 선언된 모든 final 필드가 포함된 생성자를 생성해 준다.
- final이 없는 필드는 생성자에 포함되지 않는다.
잘 동작하는지 확인하기 위해 Test한다.
import com.seunggyu.book.springboot.web.Dto.HelloResponseDto;
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와 같이 메서드를 이어서 사용할 수 있다.
HelloController에도 새로 만든 ResponseDto를 사용하도록 코드를 추가한다.
@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이란 이름으로 넘긴 파라미터를 메서드 파라미터 name에 저장하게 된다.
마찬가지로 Test한다.
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.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)
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 = 100;
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 응답 값을 필드별로 검증할 수 있는 메서드다.
- $를 기준으로 필드명을 명시한다.