[Spring] Test Code

김민범·2024년 12월 15일

Spring

목록 보기
26/29


테스트 커버리지 60퍼 이상을 맞추는 과제를 진행하다 오기가 생겨서...
100%에 도전해보기로 했다.

@Generated 사용은 비밀...

주말을 갈아넣어 결국 완성해낸 100%!!
Missed Branches 에서 몇개 빠졌다고는 하는데... 내가 봤을 땐 다 검사가 진행된 부분 같아서 수정하지 못하고 월요일에 질문을 해봐야 할 것 같다.

테스트 코드를 작성하며 어려웠거나 고민됐던 부분들을 정리해보자.
그때그때 정리를 하지 못했어서 기억에 의존해 글만 작성되는 부분이 많을 것 같다.

1. Interceptor

controller 테스트를 진행하며 로그인, 혹은 인가가 필요한 요청에서 테스트를 진행할 때, 기존에 설정해 둔 interceptor 에 걸려서 테스트가 이루어지지 않았다.
물론 MockHttpSession 을 만들어서 로그인처리, 인가처리를 한 후 작성을 할 수 있지만 매번 그런식으로 진행하는게 이상해서 방법을 찾아보기로 했다.

처음에는 interceptor 자체를 MockitoBean 으로 등록해 기능을 없애버리고 실행하니 테스트가 잘 되었다. 그러나 인터셉터가 많아지는 경우 라던지 좀 짜치는 방법이라는 피드백을 받고 다른 방법을 찾기로 했다.

fakeInterceptor 를 생성해 테스트 클래스에서 @Import 해주어 모든 요청을 통과시키려고 했지만, TestWebConfig 를 만들어 fakeInterceptor 를 등록하고 Import 해주어도 기존 인터셉터를 패스하지 못했다. 실패한 이유는 모르겠지만 다른 방법으로 시도해보았다.

자료를 찾아보다 WebMvcTest 에서 excludeFilter 를 지정할 수 있다는 사실을 알게되었고, 다음과 같이 설정해서 문제를 해결했다.

@WebMvcTest(value = ReservationController.class,
        excludeFilters = @ComponentScan.Filter(
                type = FilterType.ASSIGNABLE_TYPE,
                classes = {
                        WebConfig.class
                }
        ))
class ReservationControllerTest {
	// test code
}

이런식으로 처리하니 요청이 인터셉터에 걸리지 않고 넘어왔다.
모든 컨트롤러 테스트에 저 코드를 추가해주는 것이 불편해서 다음 프로젝트에서는 supportTestClass 를 abstract 로 만들어 사용해보려고 한다.

2. Repository 테스트의 Transaction

레포지토리 테스트를 작성하는 과정에서 각 테스트 메서드에 데이터를 저장하고, 해당 데이터를 이용해 메서드를 사용하는 로직을 작성했다.

ex) findUserById

userRepository.save(new User("name"));
User findUser = userRepository.findUserById(1L);
assertEquals(1L, findUser.getId());

이런식으로 로직을 작성하니, 메서드 하나씩 테스트를 진행할 때 문제가 없었지만 테스트 클래스 전체를 돌려보면 에러가 발생했다.
이유는 다음과 같다.

테스트 환경에서 @Transactional 혹은 @DataJpaTest 등이 적용된 상태에서 엔티티를 저장하면, DB의 AUTO_INCREMENT 시퀀스가 증가한 뒤, 트랜잭션 롤백으로 인해 데이터는 사라지지만 시퀀스 값은 복원되지 않는다.
그래서 다음 테스트나 실제 코드에서 새로 INSERT할 때 아이디가 ‘중간에 비는 현상’(밀리는 현상)이 발생한다.

예시
테스트 코드에서 id = 1로 사용자 저장
트랜잭션 롤백 (데이터 삭제)
다음 테스트 또는 실제 코드에서 새 데이터 저장 시 DB 아이디가 2부터 시작

이를 해결할 수 있는 방법은 다음과 같다.
1. H2 인메모리 DB를 사용 시

  • 시퀀스 리셋 스크립트를 실행하거나, 매 테스트마다 새로운 인메모리 DB를 기동하면 문제가 줄어든다.
  • 예: spring.sql.init.mode=always, schema.sql에서 DROP TABLE 등으로 매번 초기화.
  1. @DirtiesContext 사용
  • 테스트 메서드마다 Spring Context를 폐기하고 새로 생성한다.
  • 테스트 간 공유되는 DB 세션이 초기화되어 AUTO_INCREMENT 값도 매번 1부터 시작한다.
  • 단점: 테스트 수행 시간이 길어진다.
  1. Auto-Increment ID 자체를 검증하지 말기
  • 테스트에서 ID 값 자체를 단정하지 않고, 실제 저장된 엔티티의 getId()로 비교한다.
  • “아이디가 1인지”를 검증하기보다는 “아이디가 null이 아님” 혹은 “DB가 부여한 아이디와 일치”를 검증하는 게 바람직하다.
  1. 별도의 시퀀스 초기화 스크립트:
  • 매 테스트 후 ALTER TABLE table_name AUTO_INCREMENT=1(MySQL 등)으로 시퀀스 초기화.
  • 혹은 TRUNCATE TABLE ... 을 통해 테이블을 비우며 AUTO_INCREMENT 리셋.

나는 3번 방법을 사용해 해결했다.

마무리

문제가 더 있던거 같은데 일단 정리가 되는 부분만 적어봤다. 문제들을 근본적으로 해결한 부분도 있고 발등의 불만 끈 부분도 있는거 같은데 앞으로 이러한 점들을 고려하며 테스트를 작성하면 더 좋은 코드가 나올거라고 생각한다.

0개의 댓글