이 글은 이동욱 님의 스프링 부트와 AWS로 혼자 구현하는 웹 서비스를 읽고 정리한 글입니다.
1. 테스트 코드 소개
1) TDD와 단위 테스트(Unit Test)
● TDD
테스트가 주도하는 개발을 뜻한다. 테스트 코드를 먼저 작성하는 것부터 시작한다. 레드 그린 사이클을 거치며 개발한다.
레드 그린 사이클은 다음과 같다.
- 항상 실패하는 테스트를 먼저 작성 (Red)
- 테스트가 통과하는 프로덕션 코드 작성 (Green)
- 테스트를 통과하면 프로덕션 코드를 리팩토링 (Refactor)
● 단위 테스트
TDD의 첫 번째 단계인 기능 단위의 테스트 코드 작성을 뜻한다. TDD처럼 테스트 코드를 꼭 먼저 작성할 필요도 없으며, 리팩토링 또한 포함되지 않는다. 순수하게 테스트 코드를 작성하는 것만을 의미한다.
이번 게시물에서는 TDD가 아닌 단위 테스트 코드에 대해 설명하겠다.
2) 테스트 코드의 장점
저자는 경험을 토대로 테스트 코드의 3가지 장점을 설명하였다.
● 빠른 피드백
단위 테스트 없이 진행하는 개발 단계는 다음과 같다.
- 코드 작성
- 프로그램(Tomcat) 실행
- API 테스트 도구로(Postman 등) HTTP 요청
- 요청 결과를 System.out.println()으로 눈을 통해 직접 검증
- 원하는 결과가 아니면 프로그램(Tomcat) 중지 후 코드 수정
위의 과정에서 2번 ~ 5번은 코드 수정시마다 반복해야 한다. 왜냐면 테스트 코드가 없기 때문에 직접 눈과 손으로 수정된 기능을 확인할 수밖에 없어서이다.
위와 같은 번거로움을 테스트 코드로 해결할 수 있다.
● 자동 검증
System.out.println()을 통한 수동 검증이 아닌 단위 테스트의 실행만을 통해서 자동으로 검증이 가능하다.
● 개발자가 만든 기능을 안전하게 보호
A라는 기능이 이미 잘 되고 있는 프로그램이 있다고 가정하자. 여기서 B라는 새로운 기능을 추가하였을 때, B라는 기능이 잘 실행되어 서비스를 시작했다. 하지만 그러고 보니 A가 제대로 기능하지 못하는 불상사가 발생한다.
위와 같은 문제들은 규모가 큰 서비스 분야에서 빈번히 발생한다. 이런 경우를 대비하기 위해서 테스트 코드를 사용할 수 있다.
A에 대한 기본 기능과 다른 여러 기능들의 테스트 코드를 구현해 놓았다면, B를 추가하여 서비스를 시작하기 전에 A 기능에 대한 테스트 코드를 수행만 하면 위와 같은 문제가 발생하기 전에 조기에 발견할 수 있다.
이런 단위 테스트를 작성하는데 도움이 되는 프레임워크가 바로 그 유명한 JUnit이다!
2. Hello Controller 테스트 코드 작성
1) Application 코드
src/main/java에 com.cotato.study.SpringnAWS라는 패키지를 생성하고, Application이라는 Class를 생성했다.
//Application.java
package com.cotato.study.SpringnAWS;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication // 스프링 부트의 자동 설정, Spring 빈의 읽기와 생성을 자동 설정
public class Application { // 프로젝트의 메인 클래스
public static void main(String[] args) {
SpringApplication.run(Application.class, args); // 내장 WAS 서버 실행
}
}
@SpringBootApplication의 위치부터 설정을 읽어내려가므로 항상 이 클래스는 프로젝트의 최상단에 위치해야 한다.
스프링 부트에 의해 만들어진 JAR파일로 실행하여 Tomcat 설치 없이 WAS 서버를 구동할 수 있다.
언제 어디서나 같은 환경에서 스프링 부트를 배포하기 위해서 내장 WAS를 사용한다.
2) HelloController 코드
현재 작업 중인 패키지 하단에 web이라는 패키지를 생성하고, HelloController라는 Class를 생성한다.
이 파일에서는 간단한 API를 만들어 볼 것이다.
//HelloController.java
package com.cotato.study.SpringnAWS.web;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController // 컨트롤러를 JSON을 반환하도록 만들어줌
public class HelloController {
@GetMapping("/hello") // HTTP Method인 Get 요청을 받을 수 있는 API 생성
public String hello(){
return "hello"; // 화면에 hello를 띄움
}
}
과거의 @ResponseBody를 메서드마다 선언했던 것을 한 번에 대체하는 것이 @RestController이다.
또한 과거의 @RequestMapping(method = RequestMethod.GET)을 대체하는 것이 @GetMapping이다.
이 프로젝트는 /hello로 GET 요청이 들어오면 문자열 'hello'를 반환하고 화면에 띄워주는 기능을 가지게 되었다.
과연 작성한 코드가 제대로 작동할까? WAS 실행 없이 테스트 코드로 검증해 보자.
3) HelloController 테스트 코드
src/test/java에 위와 동일하게 com.cotato.study.SpringnAWS로 테스트 패키지를 생성해 준다.
그리고 그 패키지 하단에 web 패키지를 생성한다. 그 후 HelloControllerTest라는 클래스를 생성한다.
// HelloControllerTest.java
package com.cotato.study.SpringnAWS.web;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
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;
@RunWith(SpringRunner.class) // 테스트 진행 시 JUnit에 내장된 실행자 외에 SpringRunner라는 스프링 실행자를 사용, 즉 스프링 부트 테스트와 JUnit 사이의 연결자
@WebMvcTest(controllers = HelloController.class) // 많은 테스트 어노테이션 중 Web(Spring MVC)에 집중할 수 있는 어노테이션
public class HelloControllerTest {
@Autowired // 스프링이 관리하는 빈을 주입 받음
private MockMvc mvc; // 웹 API 테스트를 위함 (GET, POST 등)
@Test
public void hello가_리턴된다() throws Exception{
String hello = "hello";
mvc.perform(get("/hello")) // MockMVC를 통해 /hello 주소로 HTTP GET 요청
.andExpect(status().isOk()) // 앞선 코드의 결과의 HTTP 헤더의 상태가 OK(200)인지 확인
.andExpect(content().string((hello))); // 응답 본문의 결과가 변수 hello의 값과 일치하는지 확인
}
}
3) Controller 테스트 코드 실행
hello가_리턴된다() 메서드를 실행하여 테스트를 통과하는지 확인한다.
위와 같이 테스트를 통과함을 확인할 수 있다.
프로그램(Tomcat)을 실행하지 않고도 기능이 잘 구현됐는지 확인할 수 있는 것이 테스트 코드의 힘이다.
4) 브라우저에서 확인
위에서 테스트 코드를 사용하여 기능이 잘 구현되었음을 확인할 수 있지만, 사람은 눈으로 보지 못하면 믿지 못하는 동물 아닌가? 직접 눈으로 확인해 보자.
프로젝트에서 Application을 실행하여 프로그램을 구동한 후에, 웹 브라우저에서 "localhost:8080/hello"로 접속해 보자.
위 사진처럼 우리가 원하던 대로 API의 기능이 잘 구현되었음을 확인할 수 있다.
3. 롬복 소개 및 설치
롬복은 자바 개발 시에 자주 사용하는 Getter, Setter, 기본 생성자, toString 등을 어노테이션으로 자동 생성해 주는 자바 개발자들의 필수 라이브러리이다!
롬복 설치를 위해 build.gradle에 다음 코드를 추가하여 라이브러리를 내려받자.
compile('org.projectlombok:lombok')
라이브러리를 모두 내려받았다면, Lombok plugin을 설치한다.
플러그인 설치 후 IntelliJ IDE를 재실행하고, [파일] > [설정] > [빌드, 실행, 배포] > [컴파일러] > [어노테이션 프로세서]에서 어노테이션 활성화 체크 박스를 체크한다.
이렇게 되면 롬복 사용을 위한 준비를 마쳤다. 이제 기존 코드를 롬복으로 리팩터링 해보자.
4. HelloController 코드를 롬복으로 전환하기
만약 프로젝트가 지금처럼 작은 규모가 아니라 거대한 프로젝트였다면 롬복으로 전환하기 쉽지 않았을 것이다. 어떤 기능이 제대로 작동할지 안 할지를 예측할 수 없기 때문이다.
하지만 테스트 코드가 우리의 코드를 지켜주기 때문에, 롬복으로 변경 후 테스트 코드만 돌려봄으로써 문제가 발생하는지 확인할 수 있다.
1) HelloResponseDto 코드
먼저 web 패키지에 dto 패키지를 추가한다. 앞으로 모든 응답 Dto를 여기 dto 패키지에 추가할 것이다.
dto 패키지에 HelloResponseDto라는 Class를 생성한다.
// HelloResponseDto.java
package com.cotato.study.SpringnAWS.web.dto;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter // 선언된 모든 필드의 get 메서드 생성
@RequiredArgsConstructor // 선언된 모든 final 필드의 생성자를 생성
public class HelloResponseDto {
private final String name;
private final int amount;
}
이제 Dto에 적용된 롬복이 잘 작동하는지 간단한 테스트 코드를 통해 확인해 보자.
2) HelloResponseDto 테스트
테스트 패키지 web 하단에 똑같이 dto 패키지를 생성한다. 그 후 해당 패키지에 HelloResponseDtoTest라는 Class를 생성한다.
// HelloResponseDtoTest.java
package com.cotato.study.SpringnAWS.web.dto;
import org.junit.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의 인자로 받고, isEqualTo의 인자와 동일한지 체크
assertThat(dto.getName()).isEqualTo(name); // dto의 name과 변수 name이 동일한지 테스트
assertThat(dto.getAmount()).isEqualTo(amount); // dto의 amount과 변수 amount가 동일한지 테스트
}
}
3) HelloResponseDto 테스트 코드 실행
롬복_기능_테스트() 메서드를 실행하여 테스트를 통과하는지 확인한다.
위와 같이 테스트를 통과함을 확인할 수 있다. 정상적으로 롬복 기능이 수행되는 것을 증명하였다.
4) HelloController에 ResponseDto 코드 추가
HelloController에서 ResponseDto를 사용하도록 코드를 추가하자.
// HelloController.java
@GetMapping("/hello/dto") // HTTP Method인 Get 요청을 받을 수 있는 API 생성
public HelloResponseDto helloDto(@RequestParam("name") String name, @RequestParam("amount") int amount){
//@RequestParam은 외부에서 API로 넘긴 인수들을 가져오는 어노테이션으로, 위에서는 name(@RequestParam("name"))이라는 이름으로 넘긴 인수를 name(String name)에 저장
return new HelloResponseDto(name, amount); // name, amount를 가지는 HelloResponseDto를 반환
}
name, amount는 API를 호출하는 곳에서 넘겨줄 값들이다. 새로운 API가 추가되었다.
5) HelloController에 ResponseDto 코드 추가
우선 책과는 다르게 Hamcrest.Matchers.is 메서드를 바로 가져올 수가 없어서 의존성을 build.gradle에 추가하였다.
// build.gradle
dependencies {
testImplementation 'org.hamcrest:hamcrest:2.2'
}
추가된 API 기능을 테스트하는 코드를 HelloControllerTest에 추가하자.
// HelloControllerTest.java
package com.cotato.study.SpringnAWS.web;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.hamcrest.Matchers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@RunWith(SpringRunner.class) // 테스트 진행 시 JUnit에 내장된 실행자 외에 SpringRunner라는 스프링 실행자를 사용, 즉 스프링 부트 테스트와 JUnit 사이의 연결자
@WebMvcTest(controllers = HelloController.class) // 많은 테스트 어노테이션 중 Web(Spring MVC)에 집중할 수 있는 어노테이션
public class HelloControllerTest {
@Autowired // 스프링이 관리하는 빈을 주입 받음
private MockMvc mvc; // 웹 API 테스트를 위함 (GET, POST 등)
... // hello가_리턴된다() 생략
@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)));
}
}
6) HelloResponseDto 테스트 코드 실행
helloDto가_리턴된다() 메서드를 실행하여 추가된 API에 대한 테스트를 실행해 보자.
JSON이 리턴되는 API 역시 정상적으로 테스트를 통과하는 것을 확인할 수 있다.
'개발 > 스프링 부트와 AWS로 혼자 구현하는 웹 서비스' 카테고리의 다른 글
[Spring Boot & AWS] Chpt 6 - AWS 서버 환경을 만들어보자 - AWS EC2 (0) | 2023.01.24 |
---|---|
[Spring Boot & AWS] Chpt 5 - 스프링 시큐리티와 OAuth 2.0으로 로그인 기능 구현하기 (0) | 2023.01.23 |
[Spring Boot & AWS] Chpt 4 - 머스테치로 화면 구성하기 (0) | 2023.01.18 |
[Spring Boot & AWS] Chpt 3 - 스프링 부트에서 JPA로 데이터베이스 다뤄보자 (0) | 2023.01.13 |
[Spring Boot & AWS] Chpt 1 - 인텔리제이로 스프링 부트 시작하기 (0) | 2023.01.11 |