hola 개발

[ Test ] 제대로 익히는 Spring Boot Test 방법 #3. 통합 테스트 본문

프레임워크/스프링

[ Test ] 제대로 익히는 Spring Boot Test 방법 #3. 통합 테스트

hola. 2025. 7. 15. 15:47

[ 들어가며 ]

지난번 글인 단위 테스트에 이어 통합 테스트에 대한 글입니다. 

이번 글에서는 'Spring Boot에서 통합 테스트는 무엇이고 왜 필요한지?' 더 나아가 '어떻게 통합 테스트를 할지?' 에 대해 제가 공부하고 생각한 방법을 나누겠습니다.

 

[ 통합 테스트는 무엇이고 왜 필요할까? ] 

지난 글까지 단위 테스트에 대해 글을 적었습니다.

단위 테스트란 쉽게 말해 각 클래스의 로직을 검증하는 것이였습니다. 하지만 단위 테스트만으로는 계층 간 연동 문제를 발견할 수 없습니다. 따라서 계층 간의 연동이 의도한대로 동작하는지 검증하는 테스트가 필요한데 그것이 바로 통합 테스트 입니다.

 

즉, 통합 테스트란 스프링 애플리케이션의 다양한 모듈이 의도한대로 작동하는지 테스트 하는 것이며

통합테스트가 필요한 이유는 모듈간의 연동이 올바르게 작동함을 보장하기 때문입니다.

 

쉽게 말하면 

-단위 테스트는 -> 혼자서 잘 동작하는지

-통합 테스트는 -> 함께 잘 동작하는지

를 확인하는 것입니다.

 

[ 스프링 통합 테스트에서는 어떤 것들을 테스트 하는 걸까? ]

1. 구성 검증 : 스프링 컨텍스트 구성이 올바르게 로드되는지 확인

2. 구성 요소 간 통신 : 컨트롤러,서비스,리포지토리 및 메시징 시스템과 같은 다양한 구성요소 간 상호작용 테스트

3. 데이터베이스 통합: 데이터베이스와 올바르게 상호작용하는지 확인

4. 외부 서비스 : REST API나 서드파티 라이브리러와 같은 외부 서비스와 상호작용이 예상대로 작동하는지(외부 서비스란 spring 애플리케이션 내부가 아닌 별도로 운영되는 시스템이나 API 예를 들어 카카오톡 API, redis 등,S3 파일 업로드)

 

[ 어떻게 통합 테스트를 하면 좋을까? ]

통합 테스트를 통합 테스트에 대한 4가지의 방법론이 있습니다.

 

1.Big Bang Integration

모든 모듈을 한 번에 통합한 후 테스트 수행

@SpringBootTest로 전체 앱을 띄우고 E2E 시나리오 테스트

 

장점 : 구현이 쉽고 빠름

단점 : 디버깅이 어렵고 실패 원인 파악이 어려움

 

2.Top-down Integration

상위 계층부터 구현 및 테스트를 시작하고, 하위계층을 Stub 또는 Mock으로 대체하다가 점진적으로 통합

@WebMvcTest, MockBean으로 하위 레이어 대체하며 Controller부터 검증 

 

장점 : 사용자 흐름 중심 테스트 가능,주요 기능을 테스트 가능

단점 : 하위 계층이 없을 경우, Stub/Mock 구현 비용이 

 

3. Bottom-up Integration

하위 계층부터 개발 및 통합하며 상위 계층을 점진적으로 붙여 테스트

@DataJpaTest, Testcontainers 등으로 DB->Repository 검증부터 시작

 

장점 : DB,외부 시스템 등의 안전성 확인에 유리, 리포지토리/인프라 계층 테스트에 집중 가능

단점: 전체 기능이 통합되기 전까지 사용자 시나리오 테스트가 어려움

 

4. Sndwich Intergration

Top-down + Bottom-up 통합을 병행, 핵심 기능은 상위부터, 공통 모듈은 하위부터 동시에 개발/통합

Web + DB를 병렬로 개발/테스트 후 서비스 계층에서 통합 

 

장점: 병렬 개발 가능 -> 생산성 높아짐 , 문제 원인 분석에 상대적으로 유리

단점: 설계와 커뮤니케이션이 명확하지 않으면 혼란 발생 가능

 

위 4가지는 방법론 입니다.

어떻게 통합 테스트를 진행하면 좋을지에 대한 가이드라인 입니다.

딱 저 방법에 맞춰서 테스트를 진행하라기 보다는 각각의 테스트 목표와 테스트 시나리오에 따라 참고하셔서 테스트를 진행하면 됩니다. 

 

[ 통합 테스트 시 필요한 주요 어노테이션 ]

- @SpringBootTest

 통합 테스트는 실제 애플리케이션의 실행 환경과 동일하게, 여러 모듈이 의도대로 연동되는지를 확인하는 것이 목적이므로 전체 ApplicationContext 로딩이 반드시 필요합니다.

@SpringBootTest는 전체 애플리케이션 컨텍스트를 로딩하여 실제 애플리케이션 실행과 유사한 구조로 테스트를 진행할 수 있게 해줍니다. 

 

- @Transactional

실제 애플리케이션 실행과 유사한 구조로 테스트 진행할 때 테스트 데이터가 실제 DB에 반영되지 않아야 합니다.

@Transactional는 각 테스트가 실행된 후 자동으로 롤백 되도록 해줍니다.

 

- @AutoConfigureMockMvc

MockMvc를 통합 테스트에서 사용 가능하게 해줍니다. 

 

- @ActiveProfiles("test")

테스트용 application-test.yml 또는 application-test.properties 사용하여 실제 운영 설정과 분리된 환경에서 테스트할 수 있게 해줍니다. 

 

[ 계층별 통합 테스트 ] 

1. Controller 계층 통합 테스트

목적 : 웹 애플리케이션의 엔드포인트가 예상한 대로 작동하는지 확인하며 그 과정에서 다른 계층과 연동까지 검증

 

 통합 테스트는 전체 컨텍스트를 로드하여 컨트롤러와 연동된 계층들이 예상대로 함께 작동하는지 테스트하게 됩니다. 

이 과정에서 Controller와 연동된 Service와 Repository 계층까지 포함되어 예상대로 작동하는지 테스트하게 됩니다.

하지만 이 통합 테스트는 예상대로 작동이 되었다면 Service와 Repository가 정상적으로 연동됨을 확인할 수 있습니다.

 

그러나 이 테스트의 목적은 엔드포인트가 예상한대로 작동하는지 확인하는 통합 테스트이므로 Service와 Repository의 통합테스트는 따로 작성하는 것이 명시적으로 보여줄 수 있다는 점에서는 좋습니다.

@SpringBootTest
@AutoConfigureMockMvc
@Transactional
public class MemberJoinIntegrationTest {
    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private MemberService memberService;

    @Autowired
    private MemberRepositoryJpa memberRepositoryJpa;

    @Test
    @DisplayName("join 요청 처리 통합 테스트")
    void shouldReturnStatus201_whenJoinSuccess() throws Exception {
        // given
        MemberDto memberDto = new MemberDto("test");

        String json = objectMapper.writeValueAsString(memberDto);

        // when & then
        mockMvc.perform(post("/members")
                        .contentType(MediaType.APPLICATION_JSON)
                        .content(json)
                        .accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isCreated());
    }

}

 

2. Service  계층 통합 테스트

목적 : Service 계층이 Repository 계층과 같은 다른 구성 요소와 올바르게 연동되는지 검증

    @Test
    @DisplayName("서비스 계층 회원가입 성공 후 Repository 저장 여부 검증")
    void shouldReturnMemberId_whenMemberServiceJoinSuccess(){
        // given
        MemberDto memberDto = new MemberDto("test");

        // when
        String joinedMemberId = memberService.join(memberDto);

        // then
        Optional<Member> savedMember = memberRepositoryJpa.findById(joinedMemberId);
        Assertions.assertTrue(savedMember.isPresent());
        Assertions.assertEquals(joinedMemberId,savedMember.get().getId());
    }

 

 

[ 정리 ]

 통합 테스트 코드를 작성하면서  굳이 테스트 코드 작성하지 않고 'Post man으로 테스트 하면 되는 것 아닌가?' 하는 생각도 들었습니다. 물론 Post man으로 REST API 를 요청하고 응답값을 확인할 수 있지만, 테스트 코드는 현재 정상 작동 검증을 너머 버전 업데이트나 요청 사항 변경으로 코드 수정이 생길 경우 이점이 있을 수 있겠다는 생각이 들었습니다. 왜냐하면 코드 수정 후 애플리케이션을 실행 시키기 전에 문제가 있는 부분을 테스트 코드에서 발견할 수 있기 때문입니다. 그렇기에 테스트 코드를 잘 작성해두는 것의 중요함을 생각해 볼 수 있었습니다.