Kotest 코루틴(Coroutines)

Kotest에서 코루틴을 테스트하는 방법에 대해서 설명하다.

코루틴 디스패처 테스트

TestDispatcher는 개발자 가 가상 ​​시계를 제어하고 지연을 건너뛸 수 있도록 하는 kotlinx-coroutines-test 모듈 에서 제공하는 특수 CoroutineDispatcher 이다.

TestDispatcher는 개발자가 가상 클럭을 제어하고 지연을 건너뛸 수 있도록 하는 kotlinx-coroutines-test 모듈에서 제공하는 특수 CoroutineDispatcher이다.

TestDispatcher는 다음 작업을 지원한다.

  • currentTime은 현재 가상 시간을 가져온다.
  • runCurrent()는 이 가상 시간 시점에 예약된 작업을 실행한다.
  • advanceUntilIdle()은 더 이상 대기 중인 작업이 없을 때까지 대기 중인 모든 작업을 실행한다.
  • advanceTimeBy(timeDelta)는 현재 가상 시간이 timeDelta만큼 앞당겨질 때까지 큐에 대기 중인 작업을 실행한다.

테스트에 TestDispatcher를 사용하려면 테스트 구성에서 coroutineTestScope를 활성화하면 된다:

package com.devkuma.kotest.tutorial.coroutines.ex1

import io.kotest.core.spec.style.FunSpec

class TestDispatcherTest : FunSpec() {
    init {
        test("foo").config(coroutineTestScope = true) {
            // this test will run with a test dispatcher
        }
    }
}

이 테스트 내부에서 testCoroutineScheduler라는 Extension를 통해 스케줄러에 대한 핸들을 검색할 수 있다. 이 스케줄러를 사용하여 시간을 조작할 수 있다:

import io.kotest.core.test.testCoroutineScheduler

class TestDispatcherTest : FunSpec() {
   init {
      test("advance time").config(coroutineTestScope = true) {
        val duration = 1.days
        // launch a coroutine that would normally sleep for 1 day
        launch {
          delay(duration.inWholeMilliseconds)
        }
        // move the clock on and the delay in the above coroutine will finish immediately.
        testCoroutineScheduler.advanceTimeBy(duration.inWholeMilliseconds)
        val currentTime = testCoroutineScheduler.currentTime
      }
   }
}

스펙 레벨에서 coroutineTestScope를 true로 설정하여 스펙의 모든 테스트에 대해 테스트 디스패처를 활성화할 수 있다:

class TestDispatcherTest : FunSpec() {
   init {
      coroutineTestScope = true
      test("this test uses a test dispatcher") {
      }
      test("and so does this test!") {
      }
   }
}

마지막으로 ProjectConfig를 사용하여 모듈의 모든 테스트에 대해 테스트 디스패처를 활성화할 수 있다:

class ProjectConfig : AbstractProjectConfig() {
  override var coroutineTestScope = true
}

코루틴 디버깅

kotlinx-coroutines-debug는 JVM에서 코루틴에 대한 디버깅 기능을 제공하는 모듈이다. 활성화하면 디버그 에이전트가 ByteBuddy에 의해 설치되고 코루틴이 생성, 시작, 일시 중단 및 다시 시작될 때 정보를 캡처한다.

Kotest는 테스트별로 디버깅을 활성화할 수 있는 기능을 제공한다. 테스트 구성에서 coroutineDebugProbe를 활성화하면 이 기능을 사용할 수 있다.

이 기능을 활성화하면 테스트 내에서 시작된 모든 코루틴은 테스트 완료 후 또는 예외가 발생하는 즉시 ‘coroutine dump’에 포함된다.

class CoroutineDebugging : FunSpec() {
   init {
      test("foo").config(coroutineDebugProbes = true) {
         someMethodThatLaunchesACoroutine() // launches a new coroutine
      }
   }
}

‘coroutine dump’는 다음과 같다.

Coroutines dump 2021/11/27 22:17:43

Coroutine DeferredCoroutine{Active}@71f1906, state: CREATED
    (Coroutine creation stacktrace)
    at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:122)
    at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:30)
    at kotlinx.coroutines.BuildersKt__Builders_commonKt.async$default(Builders.common.kt:82)
    at kotlinx.coroutines.BuildersKt.async$default(Unknown Source)
    at com.sksamuel.kotest.engine.coroutines.Wibble$1.invokeSuspend(CoroutineDebugTest.kt:37)
    at com.sksamuel.kotest.engine.coroutines.Wibble$1.invoke(CoroutineDebugTest.kt)

스펙 레벨 구성

스펙 내에서 coroutineDebugProbes 설정을 재정의하여 스펙의 모든 테스트에 대해 코루틴 디버깅을 활성화할 수 있다:

class CoroutineDebugging : FunSpec() {
  init {

    coroutineDebugProbes = true

    test("foo") {
      // debugging enabled here
    }

    test("bar") {
      // debugging enabled here
    }

  }
}

프로젝트 전체 구성

ProjectConfig를 사용하여 프로젝트의 모든 테스트에 대해 코루틴 디버깅을 활성화할 수 있다:

class ProjectConfig : AbstractProjectConfig() {
  override val coroutineDebugProbes = true
}

참조




최종 수정 : 2024-04-24