Spring 애플리케이션의 테스트는 띄우는 범위에 따라 단위 테스트, 슬라이스 테스트, 통합 테스트, E2E 테스트로 나눠 부른다. 이 네 단계는 Spring이 정한 규칙이 아니라 테스트를 나눠 생각하기 위한 분류다. 흔히 말하는 테스트 피라미드는 단위, 통합, E2E 세 단계로 나누고 슬라이스 테스트를 통합 테스트의 한 종류로 보기도 한다.

네 단계

단계Spring에서 쓰는 도구띄우는 것속도
단위JUnit만클래스 하나가장 빠름
슬라이스@WebMvcTest, @DataJpaTest 등계층 하나에 필요한 빈빠름
통합@SpringBootTest애플리케이션 전체 컨텍스트느림
E2EPlaywright, Cypress 등프론트엔드부터 DB까지 실제 시스템 전체가장 느림

단위 테스트

Spring 컨텍스트(빈을 만들고 연결해 두는 Spring의 객체 관리 공간)를 띄우지 않고, 클래스나 함수 하나를 떼어 검증한다. 계산이나 파싱처럼 입력과 출력만으로 맞고 틀림을 판단할 수 있는 로직에 쓴다.

kotlin
class DiscountCalculatorTest {
    @Test
    fun `10000원에 10퍼센트 쿠폰을 쓰면 9000원이다`() {
        // Spring 없이 객체를 직접 만들어 호출함
        val price = DiscountCalculator().apply(price = 10_000, percent = 10)
        assertEquals(9_000, price)
    }
}

슬라이스 테스트

애플리케이션 전체가 아니라 계층 하나에 필요한 빈만 골라 띄운다. 어떤 계층을 고르느냐에 따라 종류가 나뉘고, 무엇을 띄우는지도 다르다.

  • @WebMvcTest: 컨트롤러, 필터, 예외 처리기처럼 웹 계층 빈만 띄운다. 서비스와 DB는 띄우지 않는다. 요청이 컨트롤러에 제대로 연결되고 응답이 기대한 모양으로 나가는지를 검증한다. 자세한 내용은 @WebMvcTest와 MockMvc에 정리했다.
  • @DataJpaTest: 리포지토리와 JPA 설정만 띄운다. 웹 계층은 띄우지 않지만 DB는 띄운다. 기본값은 내장 DB(H2 같은 메모리 DB)로 바꿔 끼우는 것이고, 각 테스트를 트랜잭션으로 감싸 끝나면 롤백한다.
  • 그 밖에 JSON 직렬화만 보는 @JsonTest, HTTP 클라이언트만 보는 @RestClientTest 등이 있다.

슬라이스 어노테이션은 한 테스트 클래스에 두 개를 함께 붙일 수 없다. 다른 슬라이스의 기능이 필요하면 @AutoConfigureMockMvc처럼 해당 @AutoConfigure... 어노테이션을 따로 추가한다.

공식 문서: Spring Boot, Testing Spring Boot Applications

통합 테스트

@SpringBootTest는 애플리케이션 전체 컨텍스트를 띄운다. 기본값(webEnvironment = MOCK)은 웹 애플리케이션 컨텍스트만 만들고 내장 톰캣 서버는 띄우지 않는다. 실제 포트로 요청을 받아야 하면 RANDOM_PORT나 DEFINED_PORT를 지정한다.

여러 컴포넌트가 함께 얽혀야 확인할 수 있는 시나리오에 쓴다. 범위는 넓지만 여전히 백엔드 서버 하나 안에서 끝나는 테스트다.

E2E 테스트

E2E(End-to-End) 테스트는 백엔드 하나가 아니라 시스템 전체를 가로지른다. 브라우저에서 버튼을 누르고, 그 요청이 API를 거쳐 DB에 저장되는지까지 확인한다. 도구도 JUnit이 아니라 Playwright나 Cypress 같은 브라우저 자동화 도구를 쓴다.

이름보다 실제로 쓰는 것

단계의 경계는 겹친다. @DataJpaTest는 이름으로는 슬라이스 테스트지만, 서비스 빈을 @Import로 함께 띄우고 실제 MySQL에 붙이면 실질은 통합 테스트에 가깝다. 그래서 테스트가 어느 단계인지는 이름보다 아래 세 가지로 판단하는 편이 정확하다.

  1. Spring 컨텍스트를 띄우는가
  2. 실제 DB에 붙는가
  3. 실제 HTTP 요청을 쓰는가 (MockMvc는 HTTP를 흉내 낸 요청이다)
kotlin
// 이름은 슬라이스지만 서비스까지 띄우고 실제 MySQL에 붙음
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Import(OrderService::class)
class OrderServiceTest { /* ... */ }

실제 MySQL을 테스트에서 띄우는 방법은 Spring Boot에서 Testcontainers 쓰기에 정리했다.

단계를 고르는 기준

새 코드마다 모든 단계의 테스트를 쓰지 않는다. 그 코드에서 검증하려는 것이 무엇인지에 맞는 단계 하나를 고른다.

  • 계산과 파싱 로직: 단위 테스트
  • 새 엔드포인트의 요청과 응답 연결: @WebMvcTest로 대표 성공 경로나 대표 실패 경로 하나
  • 저장이 실제 테이블에 들어가는지: @DataJpaTest와 실제 DB
  • 여러 컴포넌트가 얽힌 시나리오: @SpringBootTest

단위 테스트는 로직 자체가 맞는지를, 슬라이스 테스트는 그 로직이 엔드포인트나 테이블에 제대로 연결됐는지를 확인한다. 단계가 올라갈수록 확인하는 범위는 넓어지고 실행은 느려진다.

테스트가 통과했을 때 정말 의도한 로직 때문에 통과했는지 확인하는 방법은 코드를 일부러 틀려 테스트 확인하기에 정리했다.