Spring Boot 실무 가이드 · Part 11

테스트

테스트를 얼마나 무겁게 띄울지 판단하고 필요한 것만 검증하기

작성 기준2026년 7월버전과 지원 현황은 이후 달라질 수 있으니 공식 문서를 함께 확인하세요.

이 파트에서 다루는 내용

테스트를 나누는 기준단위 테스트슬라이스 테스트통합 테스트와 데이터
01

테스트는 '무엇을 띄우느냐'로 나뉩니다

테스트 종류를 나누는 기준은 검증 범위가 아니라 실행 비용입니다. Spring 컨텍스트를 얼마나 띄우는지에 따라 속도가 수십 배 차이 납니다.

빠른 테스트를 많이 두고 느린 테스트를 적게 두는 것이 기본 원칙입니다. 통합 테스트만 잔뜩 만들면 빌드 시간이 길어져 아무도 테스트를 돌리지 않게 됩니다.

단위 테스트
Spring 없이

객체를 직접 생성해 메서드만 검증합니다. 밀리초 단위로 끝나므로 업무 규칙 검증의 대부분을 여기서 처리합니다.

슬라이스 테스트
일부 계층만

웹 계층이나 JPA 계층만 띄웁니다. 컨트롤러 매핑이나 쿼리 동작처럼 프레임워크가 개입하는 부분을 확인합니다.

통합 테스트
전체 컨텍스트

애플리케이션 전체를 띄웁니다. 실제 흐름을 확인할 수 있지만 느리므로 핵심 시나리오에만 씁니다.

판단 기준

이 검증에 Spring이 꼭 필요한가를 물어보면 됩니다. 조건 분기와 계산 로직이라면 컨텍스트를 띄울 이유가 없습니다. 습관적으로 @SpringBootTest를 붙이는 것이 빌드 시간을 늘리는 가장 큰 원인입니다.

02

업무 규칙은 단위 테스트로 검증합니다

서비스의 판단 로직은 의존 객체를 가짜로 대체하면 Spring 없이 검증할 수 있습니다. 생성자 주입을 쓰는 이유가 여기서도 드러납니다.

Mockito를 사용한 단위 테스트java
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {

    @Mock
    private OrderRepository orderRepository;

    @InjectMocks
    private OrderService orderService;

    @Test
    void 결제완료가_아닌_주문은_취소할_수_없다() {
        // given
        Order order = Order.of(OrderStatus.SHIPPED, 10_000);
        given(orderRepository.findById(1L)).willReturn(Optional.of(order));

        // when & then
        assertThatThrownBy(() -> orderService.cancel(1L))
                .isInstanceOf(BusinessException.class)
                .hasMessageContaining("결제 완료 상태만");
    }
}

테스트 메서드 이름을 한글로 쓰면 무엇을 보장하는지 바로 읽힙니다. 실패 목록만 봐도 어떤 규칙이 깨졌는지 알 수 있습니다.

03

슬라이스 테스트로 계층별 동작을 확인합니다

컨트롤러의 요청 매핑과 검증, 리포지토리의 쿼리 동작은 실제로 프레임워크를 거쳐야 확인됩니다. 다만 전체를 띄울 필요는 없습니다.

@WebMvcTest
웹 계층만

지정한 컨트롤러와 웹 관련 빈만 로드합니다. 서비스는 가짜 객체로 대체해 요청·응답 형식과 상태 코드를 검증합니다.

@DataJpaTest
JPA 계층만

엔티티와 리포지토리만 로드합니다. 기본적으로 각 테스트가 끝나면 롤백되므로 데이터가 남지 않습니다.

@RestClientTest
외부 호출

외부 API 호출 코드를 검증합니다. 실제 서버 없이 응답을 흉내 냅니다.

웹 계층 슬라이스 테스트java
@WebMvcTest(OrderController.class)
class OrderControllerTest {

    @Autowired
    private MockMvc mockMvc;

    // Spring Boot 3.4 이상: @MockBean 대신 @MockitoBean 을 씁니다
    @MockitoBean
    private OrderService orderService;

    @Test
    void 필수값이_없으면_400을_반환한다() throws Exception {
        mockMvc.perform(post("/api/orders")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content("{}"))
                .andExpect(status().isBadRequest())
                .andExpect(jsonPath("$.code").value("INVALID_REQUEST"));
    }
}

@MockBean은 3.4에서 사용 중단으로 표시됐고 4.0에서 제거됐습니다. 새로 작성하는 코드는 @MockitoBean을 씁니다.

04

통합 테스트는 느립니다. 그래서 관리가 필요합니다

@SpringBootTest는 애플리케이션 전체를 띄웁니다. 핵심 시나리오를 검증하는 데 유용하지만, 개수가 늘면 빌드 시간이 급격히 늘어납니다.

Spring은 같은 설정의 컨텍스트를 재사용합니다. 반대로 말하면 테스트마다 설정을 조금씩 다르게 하면 컨텍스트가 계속 새로 뜹니다. 통합 테스트가 느린 가장 흔한 이유입니다.

컨텍스트 캐싱
속도의 핵심

설정이 같으면 컨텍스트를 재사용합니다. 프로파일, 속성, 목 빈 구성이 조금만 달라도 새로 뜨므로 설정을 통일하는 편이 빠릅니다.

@Transactional 롤백
데이터 정리

테스트에 붙이면 종료 시 롤백됩니다. 다만 실제 커밋 이후 동작이나 비동기 처리는 검증할 수 없습니다.

H2로 테스트
주의

빠르지만 운영 DB와 문법과 동작이 다릅니다. H2에서 통과한 쿼리가 운영에서 실패할 수 있습니다.

Testcontainers
권장

운영과 같은 DB를 컨테이너로 띄워 테스트합니다. 느리지만 신뢰도가 높습니다. Docker 실행 환경이 필요합니다.

05

무엇을 테스트할지 정하는 것이 더 중요합니다

반드시 테스트할 것
우선순위
  • 금액, 재고, 권한처럼 틀리면 손해가 나는 계산
  • 상태 전이 규칙 (취소 가능 조건 등)
  • 경계값과 예외 상황
  • 장애가 났던 지점 (재발 방지)
테스트하지 않아도 되는 것
비용 절감
  • 단순 위임 메서드
  • 프레임워크가 보장하는 동작
  • getter·setter
  • 자주 바뀌는 화면 문구
커버리지 함정
지표 주의

숫자를 목표로 삼으면 의미 없는 테스트가 늘어납니다. 커버리지는 빠진 곳을 찾는 참고 지표이지 목표가 아닙니다.

깨지는 테스트
관리

구현을 조금만 바꿔도 실패하는 테스트는 리팩터링을 막습니다. 내부 구현이 아니라 겉으로 드러나는 동작을 검증합니다.

실무 기준

테스트의 목적은 커버리지가 아니라 '고쳐도 안심하고 배포할 수 있는 상태'를 만드는 것입니다. 바꾸기 무서운 코드가 있다면 그곳이 테스트가 가장 필요한 지점입니다.

버전

3.x와 4.x 차이

본문은 현장에서 가장 많이 쓰는 3.x 기준입니다. 4.x에서 달라진 부분만 아래에 정리합니다.

  • @MockBean과 @SpyBean은 Spring Boot 3.4에서 사용 중단으로 표시됐고 4.0에서 제거됐습니다. 대체는 @MockitoBean과 @MockitoSpyBean이며 패키지가 다릅니다.
  • 3.4 이상 프로젝트라면 지금부터 새 애노테이션으로 작성하는 편이 낫습니다. 나중에 4.x로 올릴 때 테스트 코드 전체를 수정하는 일을 피할 수 있습니다.
  • 새 애노테이션은 테스트 클래스의 필드에는 쓸 수 있지만 @Configuration 클래스에는 쓸 수 없습니다. 여러 테스트가 공유하던 목 빈 설정은 구조를 바꿔야 합니다.
체크

이 파트 완료 기준