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 등의 테스트도 지원하니 문서를 확인해보면 좋습니다.

 

 

 

참고