더 좋은 UI 테스트를 만들기 위한 Coroutine 활용 방법 - 부제 정답은 없다



개인 광고 영역

Android Studio Espresso Recorder 활용한 UI 테스트에 대해 알아보았는데, 부족한 점이 있었다. 그래서 이번 글을 통해 Espresso Recorder에서 생성해준 코드에 부족한 부분을 채워 보려고 한다.

결론부터 적어보면 Espresso Recorder Recorder를 활용하는 경우 Android UI가 다 그려졌는지에 대한 여부는 제공하지 않는다.

이전 글 : 안드로이드 Espresso Recorder 활용한 UI 테스트

완전하지는 않지만, 최소한 UI가 그려지는 시점을 알 수 있을 것 같은 방법을 알아보려고 한다.

Kotlin Coroutines 활용 방법을 소개한다.

본 글에서는

  • 이전 글을 읽어보고 이 글로 돌아와야 한다.
  • 실제 UI가 그려짐을 확인하는건 아니고, 코루틴을 활용한 방법을 소개한다.


어떻게 풀까?

기존 글에서 적었듯이, 우리가 실제로 화면에 그려졌음을 캐치할 방법을 알아야 한다.

그래서 해볼 수 있는 가장 쉬운 방법으로 Thread.sleep을 활용하는 방법이다. 1초, 2초, 3초 등의 시간을 지정해두고, 서버의 다양한 환경을 테스트해볼 순 있다. 하지만 최대치 3초를 지정한다고 해도, 그 시간에 응답이 올 거라는 기대는 할 수 없다.

어디까지나 Thread.sleep은 그냥 개발자가 예측한 시간일 뿐 실제 시스템 환경을 고려한 것은 아니다.

모든 서버가 정확한 시간에 응답이 오면 좋겠지만, 그렇지는 않을 것이다.

테스트 서버 환경이 있다면 그나마 예측 치가 잘 맞을 수 있다.

모든 환경이 동일하지 않기에 최적의 방법을 찾아야 한다.

어디까지나 UI 테스트는 내가 해둔 예외 처리, 화면이 잘 그려졌는지, A의 상황에서 의도한 A 화면이 잘 노출되는지가 더 중요하기에…


Thread.sleep 대신 사용할 수 있는 것은?

조금은 귀찮지만 Coroutine을 활용 방법을 제시하는데, 이미 Coroutine을 사용하는 경우에 도움 될 수 있다.


Kotlin coroutines

Kotlin coroutines을 활용하는 방법은 매우 간단하게 접근해보려고 하는데, 한쪽에서 이벤트를 주면, 이를 받은 다른 Coroutines에서 이 데이터를 활용하는 방법인데, 이를 쉽게 해주는 Channel을 활용해보려고 한다.

sample_03

Channel에 종료한 Event의 정보를 넘겨주고, 이를 받아 UI Test에서 활용하는 간단한 방법을 활용해보았다.

어디까지나 테스트의 중점을 둔 코드라 실 사용에서 완벽하지 않을 수 있으니 단순 참고 수준으로 봐주길 바란다.


channel 생성하기

TestEvent를 처리하기 위한 object(Single class)를 생성해준다. 사용하는 쪽에서는 Coroutine 인지 RxJava 인지는 알 필요 없다. 그냥 호출만 하면 자동으로 Event를 전달하기에 필요치 않다.

외부에서는 loadSuccess, loadFail 2개의 함수를 활용할 뿐이며, 모두 내부에서 숨김 처리했다.

object TestEvent {

    val channel = Channel<Boolean>()

    private suspend fun send(channel: SendChannel<Boolean>, success: Boolean) {
        channel.send(success)
    }

    fun loadSuccess() = GlobalScope.launch {
        send(channel, true)
    }

    fun loadFail() = GlobalScope.launch {
        send(channel, false)
    }
}

생성한 channel에서는 Boolean 값만을 받도록 설정했고, true/false를 넘겨서 channel을 처리한다. 이때 Coroutine 환경을 활용하기 위해서 GlobalScope.launch를 활용했다.


channel receive 처리하기

그럼 어떻게 활용할까? 방법도 간단한데 다음과 같다.

suspend fun 화면에보여(body: suspend () -> Unit) {
    if (TestEvent.channel.receive()) {
        body()
    } else {
        throw Exception("노출하지 못해 실패")
    }
}

channel의 이벤트가 들어오기 전까지 대기한다. receive는 TestEvent에 값이 들어가기 전까지 호출되지 않는다. 그래서 UI를 다 그리는 시점에 true/false 값을 처리해야 한다.


일단은 해결했다.

여기까지 하면 UI 테스트를 시간에 의존하지 않고, 성공적으로 화면에 그려졌는지 체크할 수 있다.

다만 완벽하게 고민한 코드가 아니라서 다음과 같은 문제가 있으니, 해결 방법 중 1개로 생각해주길 바란다.

  • 현재 그려진 화면을 구분할 수 없다.(A 화면인지 B 화면인지)
  • 어떤 화면이 그려질 것인지에 대한 처리가 추가로 필요하다.
  • 매번 코드 수정이 필요할 수 있다.

코드 수정이 필요할 수 있는데, 예를 들면 A 화면을 그리는 데 있어, 엔드 포인트 위치가 달라졌다면? loadSuccess/loadFail 함수를 새로운 위치로 이동 시켜주어야하나 이 코드로는 보증하지 못한다.


또 다른 방법은?

카카오와 전혀 무관한 Agoda에서 만든 kakao UI Test 툴이 있다. 이를 활용한다면 제시한 내용과는 다르지만 UI 테스트가 좀 더 쉽다.

주요 코드를 확인해보면 처음에 제시한 Thread.sleep을 활용하고 있긴 하지만, 시간이 지나고, RootView를 추가로 확인하는 코드가 매핑되어있다.

결국 UI가 다 그려졌는지에 대한 보증을 어떻게 처리할 것인가를 잘 선택하는 게 좋다.


마무리

필자가 작성한 방법은 RxJava로도 동일하게 만들 수 있다. 이 부분을 라이브러리 화한다면 Coroutine 사용 후 자동으로 처리할 수 있거나, RxJava를 활용 후 알아서 이벤트를 처리해주는게 필요하다. 하지만 데이터의 흐름을 방해해서는 안 되기에 좀 더 고민이 필요하다.

깔끔하지는 않지만 대략 새로운 방법을 제시해보았다. 다음 글에서는 kotlin extension을 활용해서 좀 더 보기 좋은 테스트 코드 만드는 방법을 소개해보겠다.



About Taehwan

My name is Taehwan Kwon. I have developed Android for 6 years and blog has been active for eight years.

Comments