어느 날 갑자기 잘 동작하던 테스트가 안된다는 소리를 들었다.
처음엔 당연히 개인 설정 문제일 거라고 판단했고 "조금 있다 한번 봐볼게요~"라고 가볍게 넘기고 수동으로 테스트를 돌려보았다.
작년 if kakao에서 발표했던 내용들이 한순간 물거품이 되는 순간이었다. if kakao 이후 테스트 관련 빌드업이 멈췄다는 반성과 구글 나한테 왜 그래?라는 생각이 동시에 들었고 한편으로 라이트닝 토크와 글 쓸 주제가 생겼다고 기뻐했다.
아ㅏㅏㅏ..이 취소선 싫다
갑자기 테스트가 안된 이유부터 적어보면 coroutines version 1.6.0 이상부터 기존에 사용했던 kotlinx-coroutines-test 라이브러리에 많은 변화가 생겼고, 테스트 도입이 끝났으니 손 놓고 있던 나의 문제였다
여러 변경점이 있겠지만 발표했던 내용 위주로 변경사항을 정리해 보려 한다.
실행하는 코드가 지연을 건너뛸 것이라는 차이점을 제외하고는 기존에 사용하던 runBlockingTest와 유사하게 동작한다.
delay를 자동으로 건너뛰고 1초보다 훨씬 빠르게 완료된다.
고려해야 할 사항이 두 가지 있다.
- 새 코루틴을 만들 때는 적절한 TestDispatcher를 선택해서 예약되는 방식을 제어해야 한다. (?? 뒤에서 좀 더 봐보자)
- 기존과 마찬가지로 실행하는 다른 Dispatcher로 이동하면 runTest는 계속 작동하지만 delay는 더 이상 건너뛰지 않고 테스트 코드가 여러 스레드에서 실행되므로 예측하기 힘들어진다. 그래서 테스트에서는 실제 Dispatcher를 TestDispatcher로 교체해야 한다.
새 코루틴의 실행을 예측할 수 있게 새 코루틴의 경우 TestDispatchers를 사용해야 한다.
기존에는 TestCoroutineDispatcher로 퉁쳐서 다 넣어주면 되었지만 위에서 잠깐 보았던 StandardTestDispatcher와 UnconfinedTestDispatcher로 나뉘게 되었다.
runTest는 TestDispatcher를 사용하는 CoroutineScop의 구현이다. 따로 지정하지 않으면 기본적으로 StandardTestDispatcher를 사용하고 테스트 코루틴을 실행한다.
새 코루틴을 시작하면 기본 스케줄러의 대기열에 추가되고 테스트 스레드를 사용할 수 있을 때마다 실행된다. 이렇게 대기열에 추가되는 동작을 통해 테스트 중에 새 코루틴이 실행되는 방식으로 제어할 수 있다. 따로 제어하지 않으면 테스트 빌더가 반환되기 직전에 실행된다. (아래 첫 번째 이미지에서 assert가 실행되고 대기열에 추가된 새로운 코루틴이 실행된다.)
runTest는 기본적으로 StandardTestDispatcher를 사용하고 있고 새로운 코루틴을 대기열에 추가하기 때문에 실패하게 된다.
짠~
advanceUntilIdle
대기열에 남은 항목이 없을 때까지 코루틴을 모두 실행한다. 대기 중인 코루틴이 모두 실행되도록 하는 좋은 기본 선택이며 대부분의 테스트 시나리오에서 작동한다.
advanceTimeBy
주어진 양만큼 가상시간을 진행하고 가상 시간 해당 지점 전에 실행되도록 예약된 코루틴을 실행한다.
runcurrent
현재 가상 시간에 예약되었거나 이전에 보류 중인 작업을 실행한다.
이것들을 활용하면 기존에 사용했지만 이제는 사라진 pauseDispatcher와 resumeDispatcher를 대신해서 코루틴 이전과 이후 변화 값을 확인할 수 있다.
StandardTestDispatcher와 다르게 코루틴 빌더가 반환될 때까지 기다리지 않고 즉시 실행되고 대부분 UnconfinedTestDispatcher의 동작으로 테스트 코드가 간단해진다.
UnconfinedTestDispatcher는 새 코루틴을 바로 실행하지만 새 코루틴이 정지되면 다른 코루틴이 실행을 다시 시작한다. 예를 들어 새 코루틴에서 delay를 만나면 호출이 정지되고 최상위 코루틴 assert가 계속 진행하게 된다. 테스트는 실패하게 된다.
기존에 viewModelScope 때문에 만들었던 Main Dispatcher를 Test Dispatcher로 바꾸는 Rule(JUnit5는 Extension)은 동일하게 위에 나온 TestDispatcher들로 바꿔주면 된다.
다만 새 코루틴에 TestDispatcher를 추가하는 것은 어렵지 않으나 Dispatcher.setMain은 Rule로 만들어져 있어서 각 테스트 함수마다 다른 TestDispatcher를 넣어주는 게 난감하다. 대충 해결은 했지만 좋은 방법이 떠오르질 않는다.
다른 변화들은 찬찬히 살펴보려고 한다.
날 괴롭히지 마라 구굴