스프링 부트에서 테스트 코드와 롬복(lombok) 사용하기
테스트 코드는 말 그대로 프로그램을 만들기 위해 작성한 소스코드를 테스트하는 코드입니다. 이러한 테스트 코드는 단위(Unit)테스트를 위해 작성됩니다.
단위 테스트는 소스 코드의 특정 모듈 즉, 특정 함수와 메소드가 의도된 대로 정확히 작동하는지 검증하는 절차입니다.
🌈단위 테스트를 위한 테스트 코드를 작성해야 하는 이유는 다음과 같습니다.
1.단위 테스트는 개발단계 초기에 문제를 발견하게 도와줍니다.
2.단위 테스트는 개발자가 나중에 코드를 리팩토링하거나 라이브러리 업데이트 등의 작업을 할 때 기존 기능이 올바르게 작동하는지 확인할 수 있습니다.
3.단위 테스트는 기능에 대한 불확실성을 감소시킬 수 있습니다.
4.단위 테스트는 시스템에 대한 실제 문서를 제공합니다. 즉, 단위 테스트 자체를 문서로 사용할 수 있습니다.
🌈추가적으로 스프링 부트에서 테스트 코드를 작성할 때 다음과 같은 장점들이 있습니다.
1. 기능을 수정할 때마다 톰캣을 내렸다가 실행하는 일을 반복하지 않아도 됩니다.
2. System.out.println() 등 출력 메소드를 사용하여 직접 사람의 눈으로 소스코드를 검증하지 않고, 자동검증이 가능합니다.
3.개발자가 만든 기능을 안전하게 보호해 줍니다. 테스트 코드가 없다면, B라는 기능을 추가할 때 A 라는 기능의 문제가 생겼지만, 알지 못하는 경우가 많습니다. 하지만 테스트 코드는 새로운 기능이 추가될 때, 기존 기능이 잘 작동되는 것을 보장해 줍니다.
🌈테스트 코드 작성을 도와주는 프레임워크로는 xUnit이 있습니다. 이는 개발환경(x)에 따라 Unit 테스트를 도와주는 도구입니다. 스프링 부트에서는 자바용인 Junit이 사용됩니다.
테스트 코드 작성하기
이전 글에서 인텔리제이로 스프링 부트를 시작하는 방법에 대하여 알아보았습니다.(https://scshim.tistory.com/212)
기존에 만든 스프링 부트 프로젝트의 java 디렉토리를 오른쪽 클릭하여 새로운 패키지를 생성합니다.
생성된 패키지를 오른쪽 클릭하여 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);
}
}
위 코드는 앞으로 만들 프로젝트의 메인 클래스가 됩니다. @SpringBootApplication으로 인해 스프링 부트의 자동 설정, 스프링 Bean 읽기와 생성을 모두 자동으로 설정합니다. 그리고 @SpringBootApplication이 있는 위치부터 설정을 읽어가기 때문에 이 클래스는 항상 프로젝트의 최상단에 위치해야만 합니다.
main 메소드에서 실행하는 SpringApplication.run으로 인해 내장 WAS(Web Application Server)를 실행합니다. 내장 WAS란 별도로 외부에 WAS를 두지 않고 내부에서 WAS를 실행하는 것을 이야기합니다. 이렇게 되면 서버에 톰캣을 설치할 필요가 없고, 스프링 부트로 만들어진 Jar 파일(실행 가능한 Java 패키징 파일)로 실행하면 됩니다.
물론 스프링 부트에서 외장 WAS도 사용이 가능합니다. 하지만 스프링 부트에서는 내장 WAS를 사용하는 것을 권장합니다. 그 이유는 언제 어디서나 같은 환경에서의 스프링 부트를 배포할 수 있기 때문입니다.
외장 WAS를 사용하면 모든 서버는 WAS의 종류와 버전, 설정을 일치시켜야 합니다. 또한 새로운 서버가 추가되면 모든 서버가 같은 WAS 환경을 구축해야만 합니다. 이러한 비용을 줄일 수 있기 때문에 스프링 부트는 내장 WAS를 사용할 것을 권장합니다.
테스트를 위한 Controller 만들기
현재 패키지 하위에 이름이 ‘web’인 패키지를 생성합니다. 앞으로 컨트롤러와 관련된 모든 클래스를 이 패키지에 담습니다. 추가적으로 ‘web’ 패키지에 ‘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를 각 메소드마다 선언했던 것을 한번에 사용할 수 있게 해준 것입니다.
2. @GetMapping : HTTP Method인 Get의 요청을 받을 수 있는 API를 만듭니다.
과거에는 @RequestMapping(method = RequestMethod.GET)으로 사용되었습니다. 이것을 통해 프로젝트가 /hello로 요청이 오면 문자열 hello를 반환하는 기능을 가집니다.
이제 작성한 코드가 제대로 작동하는지 테스트합니다. 아래 그림 처럼 helloController 클래스 이름에 오른쪽 마우스를 클릭하고, Go To -> Test 버튼을 클릭하여 ‘helloControllerTest’라는 이름의 Junit5 버전의 테스트 클래스를 생성합니다.
그리고 아래 코드를 작성합니다.
@ExtendWith(SpringExtension.class) //1.
@WebMvcTest //2.
class HelloControllerTest {
@Autowired
private MockMvc mvc;
@Test
void hellork_리턴된다() throws Exception{
String hello = "hello";
mvc.perform(get("/hello")) //5
.andExpect(status().isOk()) //6
.andExpect(content().string(hello)); //7
}
}
위 코드의 의미는 다음과 같습니다.
1. @ExtendWith(SpringRunner.class) : 테스트를 진행할 때 Junit에 내장된 실행자 외에 다른 실행자를 실행시킵니다.여기서는 SpringRunner라는 스프링 실행자를 사용합니다. 즉, 스프링 부트 테스트와 JUnit 사이에 연결자 역할을 합니다.
2. @WebMvcTest : 여러 스프링 테스트 어노테이션 중 Web(Spring MVC)에 집중할 수 있는 어노테이션입니다. 선언할 경우 @Contoller, @ControllerAdvice 등을 사용할 수 있습니다. 단, @Service, @Component, @ControllerAdvice 등은 사용할 수 없습니다. 여기서는 컨트롤러만 사용하기 때문에 선언합니다.
3. @Autowired :스프링이 관리하는 빈(Bean)을 주입 받습니다.
4. private MockMvc mvc : 웹 API를 테스트할 때 사용합니다. 스프링 MVC 테스트의 시작점입니다. 이 클래스를 통해 HTTP GET, POST 등에 대해 API 테스트를 할 수 있습니다.
5. mvc.perform(get(“/hello”)) : MockMvc를 통해 /hello 주소로 HTTP GET 요청을 합니다. 체이닝이 지원되어 아래와 같은 여러 검증 기능을 이어서 선언할 수 있습니다.
6. .andExpect(status().isOk()) : mvc.perform의 결과를 검증합니다. HTTP Header의 Status(200, 404, 500 등)를 검증합니다. 여기서는 OK 즉, 200인지 아닌지를 검증합니다.
7. .andExpect(content().string(hello)) : mvc perform의 결과를 검증합니다. 응답 본문의 내용을 검증합니다. controller에서 “hello”를 리턴하기 때문에 이 값이 맞는지 검증합니다.
코드 작성이 끝났으면, 메소드 왼쪽의 화살표를 클릭하여 테스트 코드를 실행합니다. 그럼 다음과 같이 테스트가 통과 합니다.
주의!
아래와 같이 테스트가 있음에도 No tests found for given includes: 에러가 발생할 수 있습니다.
이럴 때는 preferences - Build, Execution, Deployment - Build Tools - Gradle 에서 Run tests using 옵션은 intellij IDEA로 변경한 후 `Apply`를 클릭해 설정을 저장합니다. 그리고 다시 테스트를 실행합니다.
롬복(Lombok)
롬복은 자바를 개발할 때 자주 사용하는 Getter, Setter, 기본생성자, toString 등을 어노테이션으로 자동 생성해주는 라이브러리 입니다. 서비스 초기 구축 단계에서 설계가 빈번히 변경될 수 있다. 이때 롬복의 어노테이션들은 코드 변경량을 최소화해준다.
롬복 추가하기
롬복을 추가하기 위해 build.gradle에 다음 코드를 입력하여 의존성 라이브러리를 다운로드합니다.
compile('org.projectlombok:lombok')
다음으로 Preferences - Plugins - Marketplace에서 Lombok을 검색하고, 설치합니다.
인텔리제이를 재시작하고, 롬복 설정 팝업이 등장하면 파란색 글씨를 클릭합니다.
이동한 페이지에서 Enable annotation processing을 체크하고, ‘ok’ 버튼을 클릭합니다.
기존 코드를 롬복으로 전환
web 패키지에 dto 패키지를 추가하고, HelloResponseDto 클래스를 생성합니다.
다음으로 HelloResponseDto 클래스에 코드를 추가합니다.
@Getter //1
@RequiredArgsConstructor //2
public class HelloResponseDto {
private final String name;
private final int amount;
}
위 코드의 의미는 다음과 같습니다
1. @Getter: 선언된 모든 필드의 get 메소드를 생성합니다.
2. @RequireArgsConstructor: 선언된 모든 final 필드가 포함된 생성자를 생성합니다. final이 없는 필드는 생성자에 포함하지 않습니다.
다음으로 Dto에 적용된 롬복이 잘 작동하는지 테스트 코드를 작성합니다.
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class HelloResponseDtoTest {
@Test
public void 롬복_기능_테스트(){
//given
String name = "";
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 같은 메소드를 이어서 사용할 수 있습니다.
2. isEqualTo: assertj의 동등 비교 메소드입니다. assertThat에 있는 값과 isEqualTo의 값을 비교해서 같을 때만 성공합니다.
여기서 Junit이 아닌 assertj의 assertThat를 사용한 이유는 다음과 같습니다.
1. Junit의 assertThat을 쓰게되면 is()와 같이 CoreMatcherts 라이브러리가 필요하지만, assertj의 assertThat은 추가적인 라이브러리가 필요하지 않습니다.
2. 자동완성이 좀 더 확실하게 지원됩니다.
이제 테스트 메소드를 실행하면, 다음과 같이 정상적으로 통과됩니다.
주의!
위 그림과 다음과 같이 @RequiredArgsConstructor 어노테이션을 추가했음에도 variable not initialized in the default constructroe 에러가발생한 경우 gradle의 롬복 의존성 코드를 다음과 같이 추가합니다.
compile('org.projectlombok:lombok')
testCompile "org.projectlombok:lombok"
annotationProcessor('org.projectlombok:lombok')
testAnnotationProcessor('org.projectlombok:lombok')
다음으로 HelloController에서 ResponseDto를 사용하도록 코드를 추가합니다. @RequestParam은 외부에서 AP로 넘긴 파라미터를 가져오는 어노테이션입니다. 여기서는 외부에서 name이란 이름으로 넘긴 파라미터를 메소드 파라미터 String name에 저장합니다.
@GetMapping("/hello/dto")
public HelloResponseDto helloDto(@RequestParam("name") String name, @RequestParam("amount") int amount){
return new HelloResponseDto(name, amount);
}
추가된 API를 테스트하는 코드를 HelloControllerTest에 추가합니다.
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.junit.jupiter.api.*;
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.*;
import static org.hamcrest.Matchers.is;
@ExtendWith(SpringExtension.class) //1.
@WebMvcTest //2.
class HelloControllerTest {
@Autowired
private MockMvc mvc;
@Test
void hello가_리턴된다() throws Exception{
String hello = "hello";
mvc.perform(get("/hello")) //5
.andExpect(status().isOk()) //6
.andExpect(content().string(hello)); //7
}
@Test
public void helloDto가_리턴된다() throws Exception{
String name = "hello";
int amount = 1000;
mvc.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만 허용되므로, 숫자/날짜 등의 데이터도 등록할 때는 문자열로 변경해야 합니다.
2. jsonPath: JSON 응답값을 필드별로 검증할 수 있는 메소드입니다. $를 기준으로 필드명을 명시합니다.
그리고 테스트를 실행하면, 정상적으로 테스트가 통과합니다.
출처
스프링 부트와 AWS로 혼자 구현하는 웹 서비스
'스프링 > 스프링부트' 카테고리의 다른 글
[Spring Boot] 오류 해결: (org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing) (2) | 2021.04.28 |
---|---|
[Spring Boot] logback을 통해 스프링부트 로그 설정 시작하기 (0) | 2021.04.04 |
AWS EC2, RDS를 통한 스프링 부트 프로젝트 배포 및 설정하기 (0) | 2021.03.28 |
[Spring Boot] 스프링부트 프로젝트에 Spring Data JPA 적용하기 (0) | 2021.01.15 |
[Spring Boot] 인텔리제이(intellij)로 스프링부트 시작하기 (0) | 2020.12.27 |
댓글