개발 공부/백엔드

DB까지 연결하는 통합 테스트

baby-t 2025. 9. 27. 14:40

https://www.inflearn.com/

 

인프런 - 라이프타임 커리어 플랫폼

프로그래밍, 인공지능, 데이터, 마케팅, 디자인등 입문부터 실전까지 업계 최고 선배들에게 배울 수 있는 곳.

www.inflearn.com

인프런 사이트의 김영한님의 강의를 보면서 작성한 글입니다.

 

이전 포스팅에서는 순수 자바 코드만을 이용해 각 계층(Repository, Service)이 독립적으로 잘 동작하는지 확인하는 **단위 테스트(Unit Test)**를 진행했습니다.

하지만 실제 애플리케이션은 여러 계층이 서로 상호작용하며 동작합니다. 이번 포스팅에서는 스프링 컨테이너와 데이터베이스까지 모두 연동하여, 실제 애플리케이션 동작과 가장 유사한 환경에서 테스트하는 통합 테스트(Integration Test) 방법을 알아보겠습니다. ⚙️


## 1. 통합 테스트 환경 설정 (@SpringBootTest)

스프링 부트 환경에서 통합 테스트를 진행하기 위해서는 테스트 클래스 상단에 @SpringBootTest 어노테이션을 붙여주어야 합니다.

  • @SpringBootTest: 이 어노테이션은 JUnit에게 "이 테스트는 스프링 부트와 함께 실행해야 해!"라고 알려주는 역할을 합니다. 테스트가 실행될 때, 실제 애플리케이션처럼 스프링 컨테이너를 생성하고 우리가 만든 모든 빈(Bean)들을 등록해 줍니다.

이제 스프링 컨테이너가 동작하므로, @Autowired를 사용하여 컨테이너에 등록된 실제 빈(Bean)을 테스트 클래스에 주입받을 수 있습니다.

Java
 
@SpringBootTest
class MemberServiceIntegrationTest {

    @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;
    
    // ... 테스트 코드 ...
}

@BeforeEach가 필요 없는 이유: 이전 단위 테스트에서는 MemberService와 MemberRepository 객체를 직접 new로 생성하고 연결해주는 코드가 @BeforeEach에 있었습니다. 하지만 통합 테스트에서는 @Autowired를 통해 스프링 컨테이너가 이미 만들어 둔 진짜 빈들을 자동으로 주입해주므로, 해당 설정 코드가 더 이상 필요 없게 됩니다.


## 2. 테스트 데이터 자동 롤백 (@Transactional)

데이터베이스가 연결된 테스트의 가장 큰 고민은 "테스트 중에 DB에 저장된 데이터를 어떻게 처리할 것인가?" 입니다. 테스트가 끝날 때마다 DB에 저장된 데이터를 직접 지워주지 않으면, 다음 테스트에 영향을 주어 결과를 신뢰할 수 없게 됩니다.

@Transactional 어노테이션은 이 문제를 매우 깔끔하게 해결해 줍니다.

  • @Transactional: 테스트 케이스에 이 어노테이션이 있으면, 각 테스트 메소드가 시작될 때 트랜잭션을 시작하고, 테스트가 끝나면 **결과와 상관없이(성공하든 실패하든) 항상 트랜잭션을 롤백(Rollback)**합니다.

즉, 테스트 중에 DB에 데이터를 아무리 많이 추가(INSERT)해도, 테스트가 끝나면 모든 것이 없던 일이 되어 원래의 깨끗한 상태로 돌아갑니다.

@AfterEach가 필요 없는 이유: 이전 단위 테스트에서는 @AfterEach를 사용하여 각 테스트가 끝난 후 메모리 저장소를 직접 비워주었습니다. 하지만 통합 테스트에서는 @Transactional이 자동으로 데이터베이스를 롤백해주므로, 수동으로 데이터를 지우는 코드가 더 이상 필요 없게 됩니다.


## 3. 통합 테스트 코드 예시

이제 두 어노테이션을 사용하여 실제 통합 테스트 코드를 작성해 보겠습니다.

Java
 
@SpringBootTest
@Transactional // 이 어노테이션이 핵심!
class MemberServiceIntegrationTest {

    @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;

    @Test
    void 회원가입() {
        // given
        Member member = new Member();
        member.setName("spring_db_test");

        // when
        Long saveId = memberService.join(member);

        // then
        // @Transactional 덕분에 DB에 반영은 되지만, 최종 커밋은 되지 않음
        Member findMember = memberRepository.findById(saveId).get();
        assertThat(member.getName()).isEqualTo(findMember.getName());
    }

    @Test
    public void 중복_회원_예외() {
        // given
        Member member1 = new Member();
        member1.setName("spring");
        
        Member member2 = new Member();
        member2.setName("spring");

        // when
        memberService.join(member1);
        
        // then
        // DB에 이미 "spring"이라는 이름의 회원이 있으므로 예외가 발생해야 함
        IllegalStateException e = assertThrows(IllegalStateException.class, 
            () -> memberService.join(member2));
        assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");
    }
}

이처럼 @SpringBootTest와 @Transactional을 함께 사용하면, 실제 운영 환경과 거의 동일한 조건에서 DB까지 연동하여 테스트하면서도, 테스트 데이터 관리를 매우 깔끔하게 자동화할 수 있습니다.

 

단위 테스트가 좋은 테스트일 확률이 더 높다고 합니다.