테스트 격리란?
테스트 격리
각 테스트가 다른 테스트의 상태에 영향을 주지도, 받지도 않게 만드는 것이다. 핵심은 한 테스트를 단독으로 돌리든, 1000개 중 하나로 돌리든, 순서를 섞어 돌리든 결과가 똑같아야 한다.
- 단독으로 돌리든
- 1000개 중 하나로 돌리든
- 순서를 섞어 돌리든
테스트 격리의 대상은 3가지가 존재한다.
- DB 같은 영속 상태
- 싱글톤 빈/static 필드 같은 메모리 상태
- 시간, 랜덤, 외부 API 같은 환경 상태
왜 테스트 격리를 해야할까?
테스트가 격리가 안되어 있다면 생기는 문제점들이 존재한다.
1. 순서 의존 order Dependency
A → B라면 통과하고 , B → A라면 실패하는 상황이라면, 테스트가 순서에 의존을 하게 된다.
2. 누적 오염
예약 생성에 대한 테스트 데이터가 남아있는 상황이라면 다음에 테스트하는 상황에서 조회했을 때 데이터의 개수에 대한 검증, 중복 검증에 대한 부분에서 오염이 발생한다.
3. 유령 통과
어제는 되던 테스트가 오늘 안된다는 상황이 발생하게 된다. 날짜에 대한 의존이 생기거나, 테스트에 대한 신뢰성이 떨어지게 된다.
이렇듯 테스트가 존재한다면 그 테스트가 신뢰할 수 있어야 한다.
어떻게 테스트 격리를 할까?
1. 트랜잭션 롤백 (@Transactional on test)
테스트 메서드 전체를 하나의 트랜잭션으로 감싸고, 끝나고 나면 commit 대신에 rollback으로, DB에 실제로 남아있지 않게 한다.
단점:
- 1차 캐시 영속성 컨텍스트 때문에 실제 DB에 안들어간 문제를 잡을 수 없다.
- 세션이 계속 열려 있어서 LazyInitializationException 같은 버그가 숨어버린다.
- 별조 스레드(비동기), REQUIRES_NEW, @Commit 코드들은 롤백의 밖이어서 격리가 되지 않는다.
2. @DirtyContext
테스트 후 ApplicationContext 자체를 버리고 새로 띄운다. DB 뿐만 아니라 모든 빈 상태가 초기화되기 때문에 격리 측면에서는 확실하다.
단점:
- 컨텍스트 재생성 비용이 커서, 속도가 확실히 느리가.
- DB 격리 용도로 사용한다면 오버킬이다.
- 싱글톤 빈 상태가 오염됐을 때처럼 정말 컨텍스트를 갈아야 할 때만 사용할 수 있다.
3. 테이블 초기화 (truncate / deleteAll)
@BeforeEach나 @Sql로 실제 DB를 비운다.
실제 commit이 일어나기 때문에 운영 동작에 가장 가깝고 롤백의 단점을 보완할 수 있다.
단점:
- 롤백보다 속도가 느리다.
- FK 제약 때문에 삭제 순서를 신경 써야한다.
- auto-increnment ID 리셋의 문제가 있다.
4. 고유 데이터 사용하기
테스트마다 충돌이 안나는 키를 써서 애초에 부딪히지 않게 한다. 이렇게하면, 정리를 안해도 되고 병렬 실행에 강력하다.
단점:
- 데이터가 계속 누적되므로 전체 조회 검증시에는 통하지 않는다.
- 그래서 격리보다는
충돌 회피에 가깝다.
상황에 따라 테스트 격리를 어떻게 적용해야할까?
쓸 때:
- 기본적으로
트랜잭션 롤백을 사용한다. 대부분의 통합 테스트는 이거로 충분하고 빠르다. - 롤백이 통하지 않을 때 (비동기, 실제 commit 후 동작 검증, 전체 조회 카운트 검증)
는
테이블 초기화로 전환한다. - 병렬 테스트를 사용하려면
고유 데이터를 보조로 사용한다.
안 쓸 때:
@DirtiesContext는 DB 격리 목적으로는 거의 사용하지 않는다. 속도가 느리고, 빈 상태 오염 등 진짜 필요할 때에만 사용한다.고유 데이터만으로 격리됐다고 믿으면 안된다. 전체 조회, 중복, 검증에서 깨지게 된다.
정리
테스트 격리란 테스트끼리 영향을 서로 안주고 받는 것이다.
테스트끼리 서로 영향을 주고 받게 된다면 다음과 같은 문제가 존재하게 된다.
- 순서 의존
- 누적 오염
- 유령 통과
이런 문제점들을 해결하기 위해서 테스트끼리 서로 격리를 하게 되는데, 테스트가 격리가 되었다는 기준을 생각한다면 다음과 같은 질문들을 생각해볼 수 있을거 같다.
- 테스트 실행 순서를 랜덤으로 섞어도 결과가 같은가? (@TestMethodOrder (MethodOrderer.Random.class)호 확인 가능
- 하나만
단독 실행해도 통과하는가 ? 병렬 실행해도 통과하는가 ?- 공유 가변 상태에 의존하지 않는가?
- 한 테스트가 다른 테스트가 만든 데이터의 존재를 가정하는가?
이 5개의 질문들을 다 통과 한다면 격리가 되었다고 봐도 좋고, 이런 격리가 된다면 더욱 더 신뢰할 수 있는 테스트가 될수 있다고 생각한다.