이 포스팅은 우아한Tech의 보스독님의 테스트 격리 영상을 정리한 내용입니다.
■ 테스트 격리란?
테스트는 순서에 상관없이 독립적으로 실행되며 결정적으로 수행되어야합니다. 테스트를 서로 격리하여 한 테스트를 실행하여도 다른 테스트에 영향을 주지 않도록 해야합니다.
■ 계층별 테스트
데이터들이 공유되기 때문에 불완전한 테스트를 작성하게 됩니다. 따라서 데이터베이스를 얼마나 의존하지 않고 테스트를 작성할지 또는 데이터 베이스 상태를 테스트 이전으로 돌릴지에 대해서 신경을 써야합니다.
● Domain(POJO) 계층
- 애플리케이션의 POJO(Model, Utils, etc...)는 JUnit으로 테스트
- 객체는 new 연산자(또는 빌더)로 간단히 인스턴스화
- 각각의 테스트가 실행되기 전에 @BeforeEach에서 인스턴스 초기화
- 데이터베이스를 사용하지 않기 때문에 격리를 걱정할 필요 없음
private Question question;
@BeforeEach
void setUp(){
question = Question.builder()
.userId(1L)
.title(TEST_QUETION_TITLE)
.content(TEST_QUESTION_CONTENT)
.build();
}
@DisplayName("조회수 초기값 확인")
@Test
void initValueOfVisits(){
assertThat(question.getVisits().getVisitCount()).isEqualTo(0L);
}
@DisplayName("조회수 증가")
@Test
void increaseVisits(){
question.increaseVisits();
assertThat(question.getVisits().getVisitCount()).isEqualTo(1L);
}
● Service 계층
- 실질적인 비즈니스 로직을 수행
- 실제 데이터베이스 사용
- 트랜잭션이 끝나면 데이터베이스 상태 변경
- 테스트 간 격리가 필요
- 실제 데이터베이스를 사용하면서 계층구조로 이루어져있기 때문에 사실상 통합 테스트가 되버림
- @Transactional을 사용해서 테스트가 종료되면 rollback 가능
- Mockito를 이용하면 실제 데이터베이스를 사용하지 않기 때문에 테스트 격리를 고민할 필요가 없다.
● Controller 계층
- @SpringBootTest
- Spring IoC로 실제 컨트롤러 빈을 사용해서 테스트
- 실제 데이터베이스 사용
- 통합 테스트
- @WebMvcTest
- MockMvc RestAPI 클라이언트 테스트 도구 사용
- 데이터베이스를 사용하지 않고 단위 테스트 수행
● Repository 계층
- @DataJpaTest
- Slice Test 진행
- In Memory로 테스트 수행
- 자동으로@Transactional(rollback=true)이 사용됨
■ 인수 테스트
- 시스템이 실제 운영 환경에서 사용될 준비가 되었는지 최종적으로 확인하는 단계
- 실제 운영환경에 맞게 서버를 띄우고 데이터베이스를 사용
- 테스트 격리를 신경쓰지 않으면 테스트가 실패하기 쉬움
- 테스트 단위가 커서 한번 실패하면 디버깅하기 까다로움
- Mock 객체가 아닌 실제 빈을 사용
● 인수 테스트 방법
1. @Transactional : 인수 테스트에서 제대로 작동하지 않음. 요청을 보내는 http client쪽과 실제 로직을 수행하는 서버 로직이 서로 다른 쓰레드에서 실행된다. 테스트 코드에서 어노테이션을 롤백 전략으로 해두어도, 다른 스레드에서 실행되는 서버 사이드 트랜잭션은 그 테스트 코드의 영향을 받지 않고 데이터베이스가 변하게 된다.
2. 매번 테스트 종료시 생성한 픽스처 및 데이터 삭제
3. 매번 테스트 종료시 테이블 Truncate
- Delete보다 Truncate가 좋은 이유
- 트랜잭션 로그 공간을 적게 차지
- 쿼리 실행시 행단위로 락을 걸지 않음
- 3-1 @Sql로 SQL 파일 실행
- 3-2 EntityManager 이용 (보스독님이 추천하는 방식)
엔티티 매니저로 쿼리를 직접 만들어서 실행하는 방식. 엔티티에 있는 테이블 이름을 가지고온 후 리스트에 저장
데이터 베이스를 주입 받고 테스트를 실행하기 직전 @BeforeEach에서 테이블 이름을 조사한 후 Truncate를 실행. 이렇게 만들어 두면 추후 엔티티가 추가되거나 삭제될때 동적으로 테이블을 조사하기 때문에 테스트 격리에 투입되는 비용을 줄일 수 있음
■ 정리
- 잘격리된 테스트는 유지보수가 수월
- 더욱 안전한 테스트 작성으로 코드의 품질 보장