
참고서에는 테스트 프레임워크를 JUnit4를 사용하고 있지만, 현재시점으로 유지보수와 새로운 기능이 추가되고 있지 않다고 하여 JUnit5를 통해 진행하였다.
우선 com.jojoldu.book.springboot 패키지에 Application 클래스를 생성하였다.
package com.jojoldu.book.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication // 스프링 부트의 자동 설정, 스프링 Bean 읽기와 생성 자동 설정
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args); // 내장 WAS 실행
}
}
해당 코드 작성하면서 패키지를 [Alt+Enter]를 통해 편하게 가져올 수 있었다.
이렇게 생성한 Application 클래스가 앞으로 만들 프로젝트의 메인 클래스가 될 예정이다.
특히, @SpringBootApplication이 있는 위치부터 설정을 읽어가기 때문에, 해당 클래스가 항상 프로젝트의 최상단에 위치해야 한다.
또한, SpringApplication.run으로 인해 내장 WAS를 실행하게 되는데, 이렇게 되면 항상 서버에 톰캣을 설치할 필요가 없게 되고, 스프링 부트로 만들어진 Jar 파일로 실행하면 된다.
이전에 학원에서 배울 때는 톰캣을 사용했었기에, 테스트 하고 오류가 날 때마다 끄고 켜는 게 얼마나 귀찮은 일인지 뼈저리게 느꼈는데, '이렇게 편한 방법이 있었으면 진작 배워서 쓸 걸'하는 생각이 든다.
게다가 언제 어디서나 같은 환경에서 스프링 부트를 배포 할 수 있기 때문에, 내장 WAS를 사용하는 것을 권장하고 있다고 한다.
이번엔 com.jojoldu.book.springboot.web 패키지에 HelloController 클래스를 생성하여 간단한 API를 만들었다.
package com.jojoldu.book.springboot.web;
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";
}
}
이번엔 src/test/java 디렉토리에 com.jojoldu.book.springboot.web 패키지를 생성하고, 그 안에 HelloControllerTest 클래스를 생성하였다.
package com.jojoldu.book.springboot.web;
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.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
public class HelloControllerTest {
@Autowired // 3
private MockMvc mvc; // 4
@Test
public void hello가_리턴된다() throws Exception {
String hello = "hello";
mvc.perform(get("/hello")) // 5
.andExpect(status().isOk()) // 6
.andExpect(content().string(hello)); // 7
}
}
@ExtendWith(SpringExtension.class)
• 테스트를 진행할 때 JUnit에 내장된 실행자 외에 다른 실행자를 실행시킨다.
• 여기서는 Spring Runner라는 스프링 실행자를 사용한다.
• 즉, 스프링 부트 테스트와 JUnit 사이의 연결자 역할을 한다.
• JUnit4에서는 @RunWith(SpringRunner.class)를 사용하며, 다중확장이 불가능하다. 하지만, @ExtendWith(SpringExtension.class)는 다중확장이 가능하여, 여러개를 중첩해서 사용이 가능하다.
@WebMvcTest
• 여러 스프링 테스트 어노테이션 중, Web(Spring MVC)에 집중할 수 있는 어노테이션이다.
• 선언할 경우 @Controller, @ControllerAdvice 등을 사용할 수 있다.
• 단, @Service, @Component, @Repository 등은 사용이 불가능하다.
• 여기서는 컨트롤러만 사용하기 때문에 선언하였다.
@Autowired : 스프링이 관리하는 빈(Bean)을 주입받는다.
private MockMvc mvc
• 웹 API를 테스트할 때 사용한다.
• 스프링 MVC 테스트의 시작점이다.
• 이 클래스를 통해 HTTP GET, POST 등에 대한 API 테스트를 할 수 있다.
mvc.perform(get("/hello"))
• mvc.perform의 결과를 검증한다.
• 체이닝이 지원되어 아래와 같이 여러 검증 기능을 이어서 선언할 수 있다.
.andExpenct(status().isOk())
• mvc.perform의 결과를 검증한다.
• HTTP Header의 Status를 검증한다.
• 200, 404, 500 등의 상태를 검증한다.
• 여기에선 200 상태인지 아닌지를 검증한다.
.andExpenct(content().string(hello))
• mvc.perform의 결과를 검증한다.
• 응답 본문의 내용을 검증한다.
• Controller에서 "hello"를 리턴하기 때문에 이 값이 맞는지 검증한다.
작성이 완료되었으면, 테스트 메소드를 실행해본다.

해당 버튼을 통해 실행하면

이렇게 테스트가 통과함을 확인할 수 있다.
테스트 코드로 검증했지만, 본 코드에서도 정상적으로 값이 출력이 되는지 확인을 해보자.
Application.java 파일로 다시 이동해서 똑같이 실행버튼을 클릭하여 실행해본다.

정상적으로 되면, 이렇게 나오지만 처음에 8080 포트를 사용 중인 프로세스가 있어서 오류가 났었다.
만약 그런 경우, 윈도우 명령프롬프트에서 netstat -ano | findstr :8080 로 8080포트를 사용 중인 프로세스를 찾으면 TCP 0.0.0.0:8080 0.0.0.0:0 LISTENING 12345 이런 식으로 나온다.
맨 뒤의 번호가 PID인데, taskkill /PID 12345 /F 를 입력하여 해당 PID의 프로세스를 종료할 수 있다.
실행이 되었다면 웹브라우저 주소창에 localhost:8080/hello로 접속하면

값이 잘 나옴을 확인할 수 있다.
롬복은 자바 개발자들의 필수 라이브러리이다. 자바를 개발할 때 자주 사용하는 코드 Getter, Setter, 기본생성자, toString 등을 어노테이션으로 자동 생성해준다.
build.gradle에서 dependencies{} 내에 아래 코드를 추가해 주고, gradle 동기화를 해준다.
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
롬복은 implementation 대신 compileOnly를 써야 런타임에 롬복이 포함되지 않고, annotationProcessor가 있어야 컴파일 타임에 롬복이 작동하기 때문에 이렇게 작성해야 한다.
그리고 플러그인 Action에서 검색해서 설치.. 를 해야하는데

이미 설치가 되어 있다.
아무래도 필수 플러그인이다 보니, 요즘은 기본으로 설치가 되어 있는 듯 하다.
프로젝트에서 롬복을 사용하기 위해 설정 > 컴파일러 > 어노테이션 프로세서에서 어노테이션 처리 활성화에 체크를 해주면 사용할 수 있게 된다.

이제 Hello Controller 코드를 롬복으로 전환해본다.
com.jojoldu.book.springboot.web.dto 패키지에 HelloResponseDto 클래스를 생성하여 아래 코드를 작성한다.
package com.jojoldu.book.springboot.web.dto;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter // 1
@RequiredArgsConstructor // 2
public class HelloResponseDto {
private final String name;
private final int amount;
}
이 Dto에 적용된 롬복이 잘 작동하는지 테스트 코드를 작성해본다.
테스트 패키지 com.jojoldu.book.springboot.web.dto에 HelloResponseDtoTest 클래스를 생성하여 아래의 코드를 작성한다.
package com.jojoldu.book.springboot.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); // 1, 2
assertThat(dto.getAmount()).isEqualTo(amount);
}
}
JUnit에서도 assertThat을 지원하지만, assertj의 assertThat을 사용했다.
현업에서도 JUnit5 + assertj 조합을 많이 사용한다고 한다. 이유는 JUnit의 assertThat을 쓰게 되면 CoreMatchers 라이브러리가 필요지만 assertj는 추가적인 라이브러리가 필요 없고, 자동완성이 좀 더 확실하게 지원된다. 또한 가독성이 좋아진다.

정상적으로 실행됨을 확인하였다.
이제 HelloController.java에도 새로 만든 ResponseDto를 사용하도록 아래 코드를 추가한다.
package com.jojoldu.book.springboot.web;
import com.jojoldu.book.springboot.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, // 1
@RequestParam("amount") Integer amount){
return new HelloResponseDto(name,amount);
}
}
이제 추가된 API를 테스트하는 코드를 HelloControllerTest에 추가한다.
@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").value(name)) // 2
.andExpect(jsonPath("$.amount").value(amount));
}
