통합 테스트 이렇게만 하자🧪

임규성·2025년 3월 7일

1. 통합 테스트를 하는 이유

"Spring IoC 컨테이너 컨텍스트가 올바르게 연결되었는지 확인 JDBC, 올바른 ORM, SQL 문법과 Hibernate 쿼리매핑등 데이터 접근 테스트 확인"

위에는 Spring 공식 문서에서 통합(integration-test)테스트를 하는 이유를 알려주는 문장입니다.

Spring-bean들의 결합실제 DB 쿼리를 요구사항에 맞게 잘 던지고 있는가 이 두가지를 테스팅하는 것이 핵심이라고 할 수 있습니다.

통합 테스트를 하는 이유를 정리해보겠습니다.

실제 요청값과 Java의 값은 다르다.

단위 테스트로는 RequestMapping, Data Binding, Type Conversion, Validation 테스트의 한계가 있습니다. 왜냐하면 테스트 코드에서 짠 인스턴스값으로 컨트롤러에 넣어주기 때문입니다.

"따라서 실제 요청값과 다르기 때문에 배포환경과는 가깝지 않습니다."


2. 통합 테스트의 종류

"실제 서블릿 컨테이너이기 때문에 Rest API 로 테스트를 해 줘야 합니다. 이를 구현하는 두 가지 방법이 있습니다."

TestRestTemplate (동기 방식)

  • 용도: 주로 리액티브 애플리케이션(비동기 서버) 테스트에 사용되지만, 전통적인 Spring MVC 컨트롤러에도 사용할 수 있음
  • 환경: @SpringBootTest와 WebEnvironment.RANDOM_PORT 또는 WebEnvironment.DEFINED_PORT와 함께 사용할 때 로컬 또는 임베디드 서버가 필요

WebTestClient (비동기 방식)

  • 용도: 실제 HTTP 서버로 RESTful 웹 서비스를 통합 테스트할 때 가장 적합
  • 환경: 전체 Spring Boot 애플리케이션 컨텍스트가 필요하며, 일반적으로 @SpringBootTest와 함께 사용

결론

물론 현재 프로젝트는 리액티브서버는 아니지만, 확장성을 고려한다면 WebTestClient방식의 역량을 쌓는다면 리액티브서버에도 적용이 가능하니 해당방식을 채택하기로 함


3. 애플리케이션 컨텍스트와 통합테스트

통합 테스트를 위해서 Junit5에서는 여러 애플리케이션 컨텍스트 설정들을 제공합니다.
또한 testContainer를 사용해 실제환경과 똑같은 환경에서 테스트 할 수 있습니다.

1. 포트 정의

해당 방식은 WebTestClient로 요청을 처리했을 때 필요합니다.

WebEnvironment.RANDOM_PORT
: Random한 port에서 병렬적으로 서버를 띄어서 테스트하는 방식 특정 포트에서만 해야하는 테스트가 없는경우 사용적절
WebEnvironment.DEFINED_PORT
: 정해져있는 port에서 서버를 띄운 테스트 방식, 만약 해당포트에 다음테스트를 해야한다면 기다렸다가 수행
특정 포트에서만 해야하는 테스트경우 사용적절 (GateWay등..)

2. @TestPropertySource

테스트에 필요한 properties파일을 수정할 때 이용해줄 수 있습니다

예시 코드

@SpringBootTest
@TestPropertySource(locations = "classpath:application-test.properties")  // 테스트 전용 properties 파일
public class MyServiceTest {

    @Autowired
    private MyService myService;

    @Test
    void testDatabaseConnection() {
        // 테스트 코드 작성
        // 예를 들어, 데이터베이스에 연결되어 있는지 확인하는 로직
    }
}

3. Test-container

DB뿐만 아니라 여러 인프라를 도커위에서 실행해볼 수 있어서 실제환경과 매우 유사하다.

code

@Testcontainers
@SpringBootTest
public class DatabaseIntegrationTest {

    // PostgreSQL TestContainer 생성
    @Container
    public static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:13")
                                                        .withDatabaseName("testdb")
                                                        .withUsername("user")
                                                        .withPassword("password");

    @BeforeAll
    static void setUp() {
        // TestContainer 시작 전에 필요한 설정 작업을 할 수 있습니다.
        System.setProperty("spring.datasource.url", postgres.getJdbcUrl());
        System.setProperty("spring.datasource.username", postgres.getUsername());
        System.setProperty("spring.datasource.password", postgres.getPassword());
    }

    @Test
    void testDatabaseConnection() {
        // 실제 데이터베이스 연결을 테스트합니다.
        // TestContainer에 띄운 PostgreSQL에 대한 테스트를 작성합니다.
    }
}

4. 통합 테스트 주의할점

트랜잭션이 불가능하다!

WebTestClient를 사용할 때 WebEnvironment.RANDOM_PORT를 사용하면 일반적으로 어노테이션 @Transactional을 사용한 트랜잭션이 불가능합니다.

따라서 AfterEach와 같은 방법으로 해당 문제를 해결해볼 수 있습니다.
code

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@Transactional  // 트랜잭션 관리
public class MyIntegrationTest {

    @Autowired
    private WebTestClient webTestClient;

    @Autowired
    private MyRepository myRepository;

    @BeforeEach
    void setUp() {
        myRepository.deleteAll(); // 테스트 데이터 초기화
    }

    @Test
    void testSomeEndpoint() {
        webTestClient.get().uri("/some-endpoint")
                     .exchange()
                     .expectStatus().isOk();
        // 트랜잭션 롤백 확인
    }

    @AfterEach
    void cleanUp() {
        myRepository.deleteAll(); // 테스트 후 DB 정리
    }
}

Ref :

  1. 스프링 공식문서-통합 테스트
  2. SpringBoot 단위/통합 테스트 환경 사례
  3. 3-1 통합테스트
profile
삶의 질을 높여주는 개발자

0개의 댓글