Search
Duplicate

이용자가 없는 공유 사물함 대여 시도 시 IndexOutOfBoundsException 발생

분야
BE
주제
BE
테스트
Spring
심각도
높음🔥
제보자
jbak
담당자
작성자
상태
처리 완료
이슈링크(optional)
작성일자
2023/07/29 12:18
공개여부
공개
글감
Spring
테스트

문제 상황

까비 채널에 이런 문의 글이 올라왔다.
문의 시간과 문의자 이름으로 로그를 찾아보니, 대여를 시도했더니, IndexOutOfBoundsException 에러가 발생하여 대여가 실패하였다.

원인

사물함의 상태와, 사물함 대여 종류에 따라 만료시간을 생성해주는 generateExpirationDate 메소드에서 공유 사물함인 경우, 기존 대여자의 만료시간으로 설정해주도록 한다.
그런데 첫번째 대여인 경우, 기존 대여자가 (당연히) 없으므로 만료시간을 무한대로 설정해주어야 하는데, 해당 로직이 없어 존재하지 않는 인덱스(0번째 인덱스)를 참조하려고 시도하여 IndexOutOfBoundsException가 발생하였다.

최종 해결

activeLentHistories.get(0)을 호출하기 전에 activeLentHistories가 비었는지를 확인하여 비어있다면 무한대의 시간을 반환해주도록 수정하였다.

그런데

우린 이미 해당 부분에 대한 테스트 코드가 있었다.
아무 문제 없이 테스트가 통과한다.
즉, 이 테스트 코드는 우리가 만든 generateExpirationDate 메소드가 공유사물함 첫번째 대여인 경우, 만료시간을 정상적으로 무한대로 설정한다고 판단한 것이다.
너무 이상해서 디버깅을 해보니, 원래라면 IndexOutOfBoundsException 에러를 던져야할 상황에서, activeLentHistories.get(0)가 에러를 던지지 않고, null을 반환하고 있었다.

왜?

생각해보면 당연하다.
List<LentHistory> activeLentHistories = mock(List.class); 실제 List를 사용하는 것이 아닌, List 인터페이스를 구현하는 가짜 객체가 만들어진다.
Mockito에서 구현하는 가짜 객체의 대부분의 메소드는 기본적으로 null을 반환하도록 하기 때문에 activeLentHistories.get(0)를 호출하더라도 IndexOutOfBoundsException 에러가 발생하지 않았던 것이다.

그럼 어떻게 해야할까?

가짜 구현 객체를 이용하여 어떤 메서드가 어떻게 호출될지, 일련의 동작이 어떻게 진행될지 등을 특정 상황에 맞게 시뮬레이션할 필요가 있을 때모킹을 하기에 적절한 상황이라고 생각한다.
그런데 java.util.List가 특별히 복잡한 기능을 제공해주는 라이브러리도 아니고, 단순히 자바에 내장된 유틸리티 라이브러리일 뿐이다.
그렇기 때문에 애초에 이 상황은 모킹을 해야하는 필요성이 별로 없는 상황이라는 생각이든다.
문제가 되는 테스트 코드에서 mock(List.class) 대신 new ArrayList<>() 로 변경해주고 다시 테스트를 돌리니 (리스트가 비어있는지 확인하는 로직은 빠져있다고 가정)
IndexOutOfBoundsException가 발생하며 정상적으로 테스트가 실패한다!

결론

정말 운이 나쁘게도 테스트로도 걸러내지지 않았고, 메인 배포 전 데브 서버에서의 휴먼 테스트에서도 걸러내지 못하여 결국 메인 서비스에서 유저에 의해 발견된 버그였다.
너무 메인 배포가 시급해서 데브 서버 테스트를 진행할 때, 단순히 테스트 코드 다 통과했으니 괜찮겠지~하고 꼼꼼하게 체크하지 못해서 생긴 버그인 것 같았다…  
다음에는 체크리스트를 만들어 배포 전에 꼼꼼히 테스트를 진행해 볼 필요성을 느끼게 되었다..
또한 소스코드를 테스트하는 것보다 더 어려운 것이 올바른 테스트 코드를 짜는 것인 것 같다고 느꼈다.
오늘같은 상황에서는 만약 정말로 모킹이 필요한 상황이라면, 테스트의 대상이 되는 코드에서 이 모킹 객체의 동작으로 인해 어떤 일이 일어나게 되는지 충분히 인지할 필요가 있을 것 같다..!

참고자료