본문 바로가기

Server/Spring

[Spring] Repository 테스트 코드 독립적으로 작성하기

반응형
SMALL

 

글을 시작하기 전에

최근 Spring Boot에서 테스트 코드를 조금씩 연습해보고 있다. 일단 통합 테스트는 제외하고, 단위 테스트 위주로 작성하고 있다.

Repository 테스트는 @DataJpaTest를 사용해서 진행하고 있는데, 불완전하게 독립적인 테스트 코드를 작성하여 각각 테스트에서는 성공하지만, 전체 테스트(build)에서는 실패하는 결과가 발생했다. 해결 과정을 글로 남겨보고자 한다.

 


문제 상황

3개의 Repository에 대해 테스트 코드를 작성하고 있다.

 

위 화면처럼 각각의 테스트는 성공한 결과이다.

 

하지만 build 같이, 전체적으로 테스트하는 과정에서는 실패한다. 왜???

 


원인 분석

여러가지 시도와 몇몇 지인들에게서 얻은 조언, GPT와의 대화 등으로 인해 원인은 비교적 빨리 알아낼 수 있었다.

일단 전체적으로 테스트할 때, 각 테스트가 병렬적으로 수행되다보니 테스트용 DB가 꼬여버린 것이다. 테스트를 순차적으로 진행하면 너무 느리기 때문에 병렬적으로 진행하게 되는 것 같다. 하지만 특정 테스트에서 사용한 DB가 미처 끝내지지 못하고, 또 다른 테스트에 영향을 주게 될 수 있다. 이것이 내가 겪은 문제였다. 테스트 간의 문제가 생겨버린 것이다.

 

테스트에 실패한 코드로, 어떤 문제가 있는 지 살펴보자.

  • 각 theme 데이터의 id를 1L, 2L, 3L로 임의로 지정해줬다.
    이렇게 직접 지정해줘버리면, JPA 테스트에서 theme의 id를 보장하지 못한다. 다른 테스트에서 먼저 테스트용 DB를 사용하고 있었으면, 후에 위 테스트가 실행되면서 1L ~3L이라는 auto-crement로 할당되는 id를 보장할 수 없다는 것이다. 예를 들어, 다른 테스트에서 이미 theme를 save한 적이 있으면, 자동 할당 id 기준은 이미 1을 넘어서 1L이라는 id 할당을 보장해주지 못한다. 이 때문에 다른 테스트에 영향을 받는 독립적이지 못한, 다른 테스트와 의존이 생겨버린 것이다.
  • save할 Entity를 만들어 줄 때 id를 할당했다.
    theme를 저장하기 전에, id를 할당해서 save하는 코드를 볼 수 있다. 어차피 id는 자동으로 할당되기 때문에 문제는 없지만, 올바른 코드는 아니기 때문에 빼는 것이 좋겠다.
  • 테스트 내용을 직관적으로 알기 어려운 이름을 썼다.
    테스트 이름 때문에 성공/실패 결과가 달라지지는 않지만, 가독성에 문제가 있다.
    "테마 리스트로 조회하면 테마 리스트에 포함된 모든 루틴들을 조회한다"라는 테스트 이름은 얼핏 보면 직관적으로 보일 수도 있다. 하지만 then 절에 있는 actual.get(0)과 actual.get(1)의 content를 보장하고 비교할 수 있는 지는 드러나지 않는다. 이 내용은 루틴을 조회할 때 content 오름차순으로 정렬한 상태인데, 정렬 부분도 테스트 제목에 포함되어야 할 것 같다.

위의 첫번 째 문제로 인해 1L, 2L id를 가진 테마는 존재하지 않고, 그렇기 때문에 assertThat문에서 테스트가 실패한 것이다.

 


문제 해결

그럼 원인을 알아냈으니 이제 문제를 해결해 볼 차례이다.

 

먼저 테마를 save하는 코드를 고쳐보자.

뒤에 루틴에 매핑할 3개의 테마를 만들어서 저장해줬다. 저장될 theme의 id는 제외해준다. 저장된 theme에 id는 자동으로 할당되어 있을 것이다. 정리하는 김에 name 데이터도 각각 다르게 넣어줬다 :)

 

다음으로 루틴을 save하는 코드이다.

content 기준 오름차순을 검증하기 위해, 조회될 content 문자열은 변수 firstGetContent, secondGetContent로 빼냈다.
각각의 routine은 save만 하기 때문에, saveAll로 코드 길이를 줄여봤다. 마찬가지로 저장될 routine의 id도 뺐다.

 

이제 when 절을 고쳐보자.

루틴을 조회할 테마의 id를 저장한 테마로 보장하기 위해, 저장된 테마의 id를 넣어줬다.

 

마지막으로 then 절이다.

위에서 오름차순 검증을 위해 만들어준 문자열 변수로 바꿔주면서 끝~ (추가로 테스트 메서드 이름도 직관적으로 변경했다.)

 

반영한 전체 코드이다.

@Test
void 테마_리스트로_조회하면_테마_리스트에_포함된_모든_루틴들을_내용content_오름차순으로_조회한다() {
	// given
	DailyTheme savedTheme1 = dailyThemeRepository.save(DailyThemeFixture.dailyTheme().name("테마1").build());
	DailyTheme savedTheme2 = dailyThemeRepository.save(DailyThemeFixture.dailyTheme().name("테마2").build());
	DailyTheme savedTheme3 = dailyThemeRepository.save(DailyThemeFixture.dailyTheme().name("테마3").build());

	String firstGetContent = "가";
	String secondGetContent = "하하";
	dailyRoutineRepository.saveAll(List.of(
			DailyRoutineFixture.dailyRoutine().content(secondGetContent).theme(savedTheme1).build(),
			DailyRoutineFixture.dailyRoutine().content(firstGetContent).theme(savedTheme2).build(),
			DailyRoutineFixture.dailyRoutine().content("미포함 루틴").theme(savedTheme3).build()
	));

	// when
	List<Long> themeIds = List.of(
			savedTheme1.getId(),
			savedTheme2.getId()
	);
	List<DailyRoutine> actual = dailyRoutineRepository.findAllByThemes(themeIds);
	
        // then
        assertThat(actual).hasSize(2);
        assertThat(actual.get(0).getContent()).isEqualTo(firstGetContent);
        assertThat(actual.get(1).getContent()).isEqualTo(secondGetContent);
}

 


테스트 결과

단위 테스트 결과는 여전히 성공이다 👍

 

build를 통해 전체적으로 테스트해도 이제 잘 성공한다 🙌

 


깨달은 점

  • 메서드만 나눈다고 다 독립적인 테스트 코드가 되는 것은 아니다.
  • 어느 정도 찾아보다가 정 모르겠으면, 열심히 질문하자. 이 문제도 주변 현업자에게 질문해서 해결할 수 있었다. 1부터 100까지 혼자서 다 할 필요도 없지만, 1부터도 물어보지 말고 50정도 혼자해봤을 때 더 못 가겠으면 질문하기! ⭐️
  • 어쨌든 성공해서 기쁘다 :D
반응형
LIST