kotlin 如何跳过单元测试中的延迟

s4n0splo  于 2023-03-13  发布在  Kotlin
关注(0)|答案(3)|浏览(162)

我正在尝试跳过运行单元测试时的延迟。
我从kotlinx.coroutines.test中找到了关于runTest的信息,但是它不起作用。
代码中有什么错误?代码等待3秒钟而不是跳过延迟。
代码:

import kotlinx.coroutines.delay
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.junit.jupiter.MockitoExtension
import java.time.LocalDateTime
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.*
import org.mockito.Mockito.*

@ExtendWith(MockitoExtension::class)
class KotlinTestSkipDelay {

  @OptIn(ExperimentalCoroutinesApi::class)
  @Test
  fun `skip delay`() {
    runTest {
      println("start")
      println(LocalDateTime.now())

      delayTest()

      println("end")
      println(LocalDateTime.now())
    }

  }

  private fun delayTest() { //System under test so cannot change
    runBlocking {
      delay(3000L)
    }
  }
}

我的应用程序中的实际代码需要一个延迟,所以它使用runBlocking()。没有suspend函数,并且delay()是从一个普通方法调用的,不在协程范围内。类似于单元测试中显示的delayTest()方法。在测试此代码时,我如何跳过此延迟?

aor9mmx1

aor9mmx11#

这里的问题是你在delayTest()中使用了runBlocking(),它启动了一个全新的协程,它不知道runTest(),正如函数runBlocking()的名字所暗示的,它阻塞了当前线程。
问题的解决方案取决于你的具体情况。上面的代码看起来像是重现问题的最小示例,所以你的具体情况可能会复杂得多。但是一般来说,如果delayTest()是从协程调用的,并且它要等待某个东西,那么它应该是一个suspend函数,并且永远不应该阻塞:

private suspend fun delayTest() {
  delay(3000L)
}

如果你在代码中使用runBlocking(),可能会从协程调用它,那么你可能会遇到比这更大的问题,包括降低性能甚至在运行时出现死锁。这就是为什么通常不鼓励在代码中使用runBlocking()的原因之一。

g6ll5ycj

g6ll5ycj2#

单元测试必须模拟除了被测单元之外的所有东西。如果被测单元在其逻辑中添加了一些延迟,那么就没有什么可做的了,你能做的最好的事情就是在那里检查你的逻辑,也许你做错了什么,也许你可以通过添加延迟的单元来解耦你的测试单元。在那之后,你将能够模拟添加延迟的单元,并从那里删除延迟。

class MyRepo(val myServiceWithDelay: MyService) {
    suspend fun fetchData() {
        doSomeCoolJob()
        myServiceWithDelay.doSomeDelayedCoolJob()
    }
}

class MyRepoTest {
    private lateinit var classUnderTest: MyRepo
    @Mock
    private lateinit var myMockedService: MyService

    @Before
    fun setUp() { 
        classUnderTest = MyRepo(myMockedService)
    }
    @Test
    fun testMyCoolLogic() = runBlocking {
        Mockito.given(myMockedService.doSomeDelayedCoolJob()).thenMockItSomehowWithoutDelay()
        classUnderTest.fetchData() // now you have no delay
    }

}

一般来说,这样做的目的是使我们的逻辑对单元测试更友好:)

6qftjkof

6qftjkof3#

您需要使用TestCoroutineScheduler。请阅读以下内容:https://kt.academy/article/cc-testing
编辑:基于注解的示例代码:

fun main() {
  val job = GlobalScope.launch { // Launch Coroutine
    val result = suspendingSomething() // Call suspending function
    onSuspendingFunctionCompleted(result) // Call non-suspending function with result
  }

  // Call non-suspending function as usual
  onSuspendingFunctionCompleted(56)
}

fun onSuspendingFunctionCompleted(result: Int) {
  println("$result")
}

suspend fun suspendingSomething(): Int {
  delay(3000)
  return 42
}

相关问题