들어가며
최근 팀에서 회원가입 로직 리팩토링 중, 기존 테스트 케이스 정리하는 작업 담당
이상하게도 어떤 테스트는 단독 실행 시 성공하지만, 전체 테스트를 돌리면 실패하는 현상 발생
로그를 뒤져보고, 디버깅까지 해도 로직에는 문제가 없었음, 원인은 Spring의 ApplicationContext 공유 때문으로 확인
문제 상황
예시코드 (회원가입 성공)
@SpringBootTest
class MemberServiceTest {
@Autowired
private MemberService memberService;
@Test
void 회원가입_성공() {
MemberRequest req = new MemberRequest("uijin", "pass123");
Member member = memberService.register(req);
assertEquals("uijin", member.getUsername());
}
}
예시코드(동일 이름 중복 저장 테스트)
@SpringBootTest
@TestPropertySource(properties = "spring.jpa.hibernate.ddl-auto=create")
class MemberDuplicateTest {
@Autowired
private MemberRepository memberRepository;
@Test
void 동일_이름_중복_저장_불가() {
memberRepository.save(new Member("James", "pass123"));
assertThrows(DataIntegrityViolationException.class, () -> {
memberRepository.save(new Member("James", "pass456"));
});
}
}
단독 실행시 통과하는 테스트들이지만 전체 실행시 '회원가입_성공' 테스트가 실패한다.
원인 분석
Spring의 @SpringBootTest는 기본적으로 ApplicationContext를 공유한다.
따라서, 하나의 테스트에서 삽입된 Bean, DB 데이터, 설정 변경 등이 다른 테스트에 영향을 줄 수 있다.
문제의 원인은 다음과 같다.
1. MemberDuplicateTest가 먼저 실행되며, DB에 "James" 회원이 저장됨
2. MemberServiceTest가 같은 컨텍스트/DB를 사용하다 보니, 중복 에러 발생
해결 방법
1. @DirtiesContext 사용하기
DirtiesContext 어노테이션으로 테스트 후 컨텍스트 폐기후 새로 만들도록 설정
@SpringBootTest
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
class MemberServiceTest {
...
}
테스트간 격리는 보장되나, 테스트 실행 속도가 느려진다
2. @TestPropertySource로 격리된 DB 사용
DB 이름을 랜덤하게 만들어 컨텍스트 분리 효과를 가져올수도 있다.
@SpringBootTest
@TestPropertySource(properties = {
"spring.datasource.url=jdbc:h2:mem:testdb-${random.uuid};DB_CLOSE_DELAY=-1"
})
class MemberServiceTest {
...
}
컨텍스트 공유하더라도 DB는 별도로 분리되기때문에, DB레벨에서의 공유 문제를 해결할 수 있다.
다만 상대적으로 설정이 복잡하고 코드 가독성이 떨어질 수 있다.
3.@Transactional 사용
@SpringBootTest
@Transactional
class MemberServiceTest {
...
}
테스트 메서드가 끝나면 자동으로 롤백된다. 단 MockBean과 같이 공유된 Bean의 상태 변경까지는 막지 못한다.
4. BeforeEach를 이용한 DB 데이터 클렌징
BeforeEach 이용하여 각 테스트 실행시 DB 데이터 클렌징을 진행
컨텍스트 분리 없이도 안정적인 테스트가 가능하나, 클렌징 누락등을 이유로 초기화 실패시 데이터 남을 수 있음
@BeforeEach
void setup() {
memberRepository.deleteAll(); // 항상 클린 상태에서 시작
}
'Spring' 카테고리의 다른 글
[Spring] Java 예외(Exception) 처리 전략 (1) | 2024.11.29 |
---|---|
[Spring] 카카오 로그인 REST API 방식 적용 및 구현 (0) | 2024.11.26 |
[Spring] 스프링 부트(Spring Boot) - H2 DB 연동하여 사용하기(with.JPA) (0) | 2024.11.22 |
[Spring] Junit 테스트시 Atomikos 커넥션풀의 Connection Name 중복 오류 (0) | 2024.07.08 |
IntelliJ에서 Spring MVC 세팅하기 (0) | 2024.03.17 |