Updated - 2024. 12. 08
지난 글에서는 테스트 코드에 대한 전반적인 내용과 단위 테스트 코드를 작성하는 방법에 대해 알아보았습니다.
이 글에서는 전체 애플리케이션을 테스트 하는 방법에 대해 알아보겠습니다.
예시 코드는 지난 글의 Guestbook REST-API를 기준으로 작성하겠습니다.
01. 통합 테스트 코드 작성
통합 테스트 코드는 전체 애플리케이션 컨텍스트(ApplicationContext )를 로드하여 실제 API 호출이 예상대로 동작하는지 확인하기 위해 @SpringBootTest를 사용합니다.
실제 환경과 유사하지만 애플리케이션이 클수록 테스트가 오래 걸린다는 단점이 있습니다.
기본 구조
먼저 테스트 파일의 기본 구조는 다음과 같습니다.
import com.example.guestbook.domain.entity.GuestbookEntity;
import com.example.guestbook.dto.GuestbookDto;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpStatus;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class GuestbookIntegrationTest {
@LocalServerPort
private int port;
@Autowired
private TestRestTemplate restTemplate;
private String getBaseUrl() {
return "http://localhost:" + port + "/api/guestbook";
}
@Test
void testCreateGuestbook() {
...
assertThat(createdEntry.getId()).isNotNull();
assertThat(createdEntry.getName()).isEqualTo("홍길동");
...
}
...
}
- @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
- 애플리케이션을 실제로 실행하고 무작위 포트에서 통합 테스트를 수행함을 의미합니다.
- @LocalServerPort
- 내장 웹 서버(Tomcat 등)를 실행하는 경우, 자동으로 할당된 포트를 얻고 싶을 때 사용하는 애너테이션 입니다.
- TestRestTemplate
- REST API를 호출하여 실제 HTTP 요청/응답을 테스트할 수 있는 유틸리티 클래스입니다.
- getBaseUrl()
- API를 호출할 경로를 작성합니다. 로컬 호스트 기준으로 작성하였고, 할당된 포트를 사용하였습니다.
- assertThat()
- AssertJ에서 제공하는 검증 메서드이며, JUnit과 함께 사용하여 더 직관적으로 검증을 할 수 있게 도와줍니다.
// 값 비교
assertThat(actualValue).isEqualTo(expectedValue); // 값이 같은지 확인
assertThat(actualValue).isNotEqualTo(expectedValue); // 값이 다른지 확인
// 컬렉션 검증
assertThat(list).hasSize(3); // 리스트 크기 검증
assertThat(list).contains("Apple", "Banana"); // 리스트에 특정 항목이 포함되어 있는지 확인
// 빈 값 검증
assertThat(actual).isNull(); // 값이 null인지 확인
assertThat(actual).isNotNull(); // 값이 null이 아닌지 확인
// 숫자 비교
assertThat(actual).isGreaterThan(10); // 값이 10보다 큰지
assertThat(actual).isLessThan(5); // 값이 5보다 작은지
// 문자열
assertThat(actual).startsWith("Hello"); // 문자열이 특정 접두어로 시작하는지 확인
assertThat(actual).endsWith("World"); // 문자열이 특정 접미어로 끝나는지 확인
assertThat(actual).contains("o"); // 문자열에 특정 문자가 포함되어 있는지 확인
위와 같이 기존 구조를 잡고, 아래에서 테스트 할 함수를 작성하면 됩니다.
하나씩 작성을 해보겠습니다.
1) POST
@Test
void testCreateGuestbook() {
// 1. 방명록 생성
var newEntry = GuestbookDto.builder()
.name("홍길동")
.message("안녕하세요!")
.build();
ResponseEntity<GuestbookEntity> createResponse = restTemplate.postForEntity(getBaseUrl(), newEntry, GuestbookEntity.class);
// POST 요청 확인
assertThat(createResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
GuestbookEntity createdEntry = createResponse.getBody();
assertThat(createdEntry).isNotNull();
assertThat(createdEntry.getId()).isNotNull();
assertThat(createdEntry.getName()).isEqualTo("홍길동");
assertThat(createdEntry.getMessage()).isEqualTo("안녕하세요!");
// 2. 생성된 항목 조회
ResponseEntity<GuestbookEntity> getResponse = restTemplate.getForEntity(getBaseUrl() + "/" + createdEntry.getId(), GuestbookEntity.class);
// GET 요청 확인
assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
GuestbookEntity retrievedEntry = getResponse.getBody();
assertThat(retrievedEntry).isNotNull();
assertThat(retrievedEntry.getId()).isEqualTo(createdEntry.getId());
assertThat(retrievedEntry.getName()).isEqualTo("홍길동");
assertThat(retrievedEntry.getMessage()).isEqualTo("안녕하세요!");
}
POST 요청을 하여 응답 값을 확인하고, 다시 GET 요청을 하여 정말로 저장이 잘 되었는지 확인하는 테스트입니다.
- postForEntity()
- POST 요청을 보내고, 응답 데이터(상태 코드, 헤더, 본문 데이터)를 ResponseEntity 객체로 반환하는 메서드입니다.
- 참고로 post() 메서드를 호출하면 단순히 처리만 할 수도 있습니다.
- getForEntity()
- 마찬가지로 GET 요청을 보내서 응답 데이터를 ResponseEntity 객체로 반환합니다.
- POST 요청한 name, message 값을 비교하여 테스트를 검증합니다.
2) UPDATE
@Test
void testUpdateGuestbook() {
// 1. 방명록 생성
var newEntry = GuestbookDto.builder()
.name("홍길동")
.message("안녕하세요!")
.build();
ResponseEntity<GuestbookEntity> createResponse = restTemplate.postForEntity(getBaseUrl(), newEntry, GuestbookEntity.class);
// 생성된 항목 가져오기
GuestbookEntity createdEntry = createResponse.getBody();
assertThat(createdEntry).isNotNull();
// 2. 방명록 수정
var updatedEntry = GuestbookDto.builder()
.name("홍길동")
.message("반갑습니다!")
.build();
restTemplate.put(getBaseUrl() + "/" + createdEntry.getId(), updatedEntry);
// 3. 수정된 항목 조회
ResponseEntity<GuestbookEntity> getResponse = restTemplate.getForEntity(getBaseUrl() + "/" + createdEntry.getId(), GuestbookEntity.class);
GuestbookEntity updatedEntity = getResponse.getBody();
// 확인
assertThat(updatedEntity).isNotNull();
assertThat(updatedEntity.getId()).isEqualTo(createdEntry.getId());
assertThat(updatedEntity.getMessage()).isEqualTo("반갑습니다!");
}
POST 요청하여 생성된 항목을 가져온 후, message 값을 수정하고 이를 조회하여 값이 변경되었는지 확인하는 테스트입니다.
- put()
- PUT 요청을 하는 메서드입니다.
- 응답 결과를 GET 요청하여 확인할 것이므로 굳이 ResponseEntity로 반환할 필요는 없어서 PUT 요청 처리만 합니다.
3) DELETE
@Test
void testDeleteGuestbook() {
// 1. 방명록 생성
var newEntry = GuestbookDto.builder()
.name("홍길동")
.message("안녕하세요!")
.build();
ResponseEntity<GuestbookEntity> createResponse = restTemplate.postForEntity(getBaseUrl(), newEntry, GuestbookEntity.class);
// 생성된 항목 가져오기
GuestbookEntity createdEntry = createResponse.getBody();
assertThat(createdEntry).isNotNull();
// 2. 방명록 삭제
restTemplate.delete(getBaseUrl() + "/" + createdEntry.getId());
// 3. 삭제된 항목 조회 시 404 응답 확인
ResponseEntity<GuestbookEntity> getResponse = restTemplate.getForEntity(getBaseUrl() + "/" + createdEntry.getId(), GuestbookEntity.class);
assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
}
POST 요청하여 생성된 항목을 다시 삭제하고, 이를 조회하여 요청을 처리할 수 없는지 확인하는 테스트입니다.
- delete()
- DELETE 요청하는 메서드입니다.
- PUT 테스트와 마찬가지로 삭제 처리만 하고 GET 요청하여 삭제 여부를 확인합니다.
- 정상적으로 삭제 되었으면 해당 엔티티는 존재하지 않으므로 404 코드가 응답될 것입니다.
4) Get List
@Test
void testGetAllGuestbook() {
// 1. 방명록 여러 항목 생성
restTemplate.postForEntity(getBaseUrl(), GuestbookDto.builder().name("홍길동").message("안녕하세요!").build(), GuestbookEntity.class);
restTemplate.postForEntity(getBaseUrl(), GuestbookDto.builder().name("이몽룡").message("반갑습니다!").build(), GuestbookEntity.class);
// 2. 모든 항목 조회
ResponseEntity<List> getResponse = restTemplate.getForEntity(getBaseUrl(), List.class);
// 확인
assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
List<?> entries = getResponse.getBody();
assertThat(entries).isNotNull();
assertThat(entries.size()).isGreaterThanOrEqualTo(2);
}
POST 요청으로 2개의 방명록을 생성하고, 리스트를 조회하는 테스트입니다.
리스트는 개수를 확인하는 방식으로 진행하였습니다.
이상으로 통합 테스트 코드를 작성하는 방법에 대해 알아보았습니다.
단위 테스트는 작은 단위로 빠르게 실행할 수 있으므로, CI/CD 파이프라인에서 빠른 피드백을 받을 수 있고,
통합 테스트는 일반적으로 더 많은 자원을 소모하므로, 일정한 주기로 실행하는 것이 좋습니다.
이 외에도 Spring Security, Webflux 등의 테스트도 지원하니 문서를 확인해보면 좋습니다.
참고
- https://docs.spring.io/spring-boot/reference/testing/spring-boot-applications.html#testing.spring-boot-applications.autoconfigured-spring-restdocs.with-mock-mvc
- https://ksh-coding.tistory.com/53