Spring 애플리케이션의 테스트는 띄우는 범위에 따라 단위 테스트, 슬라이스 테스트, 통합 테스트, E2E 테스트로 나눠 부른다. 이 네 단계는 Spring이 정한 규칙이 아니라 테스트를 나눠 생각하기 위한 분류다. 흔히 말하는 테스트 피라미드는 단위, 통합, E2E 세 단계로 나누고 슬라이스 테스트를 통합 테스트의 한 종류로 보기도 한다.
네 단계
| 단계 | Spring에서 쓰는 도구 | 띄우는 것 | 속도 |
|---|---|---|---|
| 단위 | JUnit만 | 클래스 하나 | 가장 빠름 |
| 슬라이스 | @WebMvcTest, @DataJpaTest 등 | 계층 하나에 필요한 빈 | 빠름 |
| 통합 | @SpringBootTest | 애플리케이션 전체 컨텍스트 | 느림 |
| E2E | Playwright, Cypress 등 | 프론트엔드부터 DB까지 실제 시스템 전체 | 가장 느림 |
단위 테스트
Spring 컨텍스트(빈을 만들고 연결해 두는 Spring의 객체 관리 공간)를 띄우지 않고, 클래스나 함수 하나를 떼어 검증한다. 계산이나 파싱처럼 입력과 출력만으로 맞고 틀림을 판단할 수 있는 로직에 쓴다.
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에 붙이면 실질은 통합 테스트에 가깝다. 그래서 테스트가 어느 단계인지는 이름보다 아래 세 가지로 판단하는 편이 정확하다.
- Spring 컨텍스트를 띄우는가
- 실제 DB에 붙는가
- 실제 HTTP 요청을 쓰는가 (
MockMvc는 HTTP를 흉내 낸 요청이다)
// 이름은 슬라이스지만 서비스까지 띄우고 실제 MySQL에 붙음
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Import(OrderService::class)
class OrderServiceTest { /* ... */ }실제 MySQL을 테스트에서 띄우는 방법은 Spring Boot에서 Testcontainers 쓰기에 정리했다.
단계를 고르는 기준
새 코드마다 모든 단계의 테스트를 쓰지 않는다. 그 코드에서 검증하려는 것이 무엇인지에 맞는 단계 하나를 고른다.
- 계산과 파싱 로직: 단위 테스트
- 새 엔드포인트의 요청과 응답 연결:
@WebMvcTest로 대표 성공 경로나 대표 실패 경로 하나 - 저장이 실제 테이블에 들어가는지:
@DataJpaTest와 실제 DB - 여러 컴포넌트가 얽힌 시나리오:
@SpringBootTest
단위 테스트는 로직 자체가 맞는지를, 슬라이스 테스트는 그 로직이 엔드포인트나 테이블에 제대로 연결됐는지를 확인한다. 단계가 올라갈수록 확인하는 범위는 넓어지고 실행은 느려진다.
테스트가 통과했을 때 정말 의도한 로직 때문에 통과했는지 확인하는 방법은 코드를 일부러 틀려 테스트 확인하기에 정리했다.