Day27:测试 Coroutine

Coroutine 是非同步程序的解决方案,我们将耗时的任务置放在 suspend 函式中,在正常的使用 coroutine 情况之下,这些 suspend 函式需要在 coroutine scope 中执行,而建立一个 coroutine scope 的方式有 launchasync

在前面的文章中,我们的程序都是直接跑在 main 函式,并且用 runBlocking 来建立一个 coroutine scope,runBlocking 是一个建立 coroutine 的方法,但是跟 launch 以及 async 不同的是,它可以在 main 函式上直接使用,所以它等於是一般的函式与 coroutine 之间的桥梁,另外,在 runBlocking 中的内容是会占用目前的执行绪,使用 runBlocking 之後,我们才能够在 runBlocking 里面呼叫 launch 以及 async。

那... 这个跟测试有什麽关系呢?

suspend 函式必须要放在一个 coroutine 中,如前面所说的我们用 runBlocking 建立一个 coroutine,然後我们才能使用 suspend 函式,或是使用 launch 、 async 来建立新的 coroutine。如果我们直接在测试程序码中,直接使用 runBlocking 来测试我们的 suspend 函式,那麽如果需要耗费比较多时间的函式,那就会真的需要耗费那麽长的时间。

kotlinx-coroutines-test

Coroutine 提供了一个供测试用的函式库,其中最重要的方法就是 runBlockingTest ,在 runBlockingTest 里面,提供了时间加速的功能,可以减少我们所等待的时间。

dependency

dependencies {
    testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.2'
}

例如,有一个 suspend 函式,使用 withTimeout 限制呼叫的时间。

class Day27 {
    suspend fun download(service: IService): String {
        withTimeout(1000) {
            return@withTimeout service.download()
        }
        return "fail"
    }
}

我们在这个函式 download 中,将 Service 由外部注入,那麽我们就可以在之後的测试进行我们的修改。

其中 IService 是一个介面,里面只有一个 suspend 函式 download

interface IService {
    suspend fun download(): String
}

我们在测试的程序码中,想要测试两个部分,一个是没有超时,另一个则是超时的情况。

  • 正常的情况(无超时)
internal class Day27Test {

    private lateinit var day27: Day27

    @BeforeEach
    internal fun setUp() {
        day27 = Day27()
    }

    @OptIn(ExperimentalCoroutinesApi::class)
    @Test
    internal fun downloadTest() = runBlockingTest {
        kotlin.test.assertEquals("done", day27.download(FakeService(100)))
    }
}

Imgur

其中 FakeService 如下

class FakeService(private val timeout: Long) : IService {
    override suspend fun download(): String {
        delay(timeout)
        return "done"
    }
}

其中 FakeService 如下

class FakeService(private val timeout: Long) : IService {
    override suspend fun download(): String {
        delay(timeout)
        return "done"
    }
}
  • 超时的版本如下:
	@OptIn(ExperimentalCoroutinesApi::class)
  @Test
  internal fun downloadTest_Fail() = runBlockingTest {
      var isException = false
      try {
          day27.download(FakeService(1500))
      } catch (e: TimeoutCancellationException) {
          isException = true
      }
      assert(isException)
  }

Imgur

原本需要花费 1500 毫秒的测试,利用 runBlockingTest 将 suspend 函式包在内後,时间就被加速了,直接走到 timeout,并且只花了 50 毫秒,以这次的结果来看,甚至比正常的情况还要快。

这就是 runBlockingTest 的用处。

小结

本篇文章简单介绍了 Coroutine 测试函式库 kotlinx-coroutines-test ,在这里面包含了 runBlockintTest ,我们将 suspend 函式放进 runBlockintTest 的区块中就可以进行测试。

除了可以将 suspend 函式带入外,它还有另外一个功能,那就是时间加速 (advance time)。在 runBlockingTest 的区块中,如果有 suspend 函式,那麽它就会加速它直到遇到 timeout。

kotlinx-coroutines-test 目前还有很多项目都被标为 ExperimentalCoroutinesApi ,在正式版出来之前,这些内容都有可能会更改,使用上要特别注意。

参考资料

kotlinx.coroutines/kotlinx-coroutines-test at master · Kotlin/kotlinx.coroutines

KotlinConf 2019: Testing with Coroutines by Sean McQuillan

特别感谢

Kotlin Taiwan User Group

Kotlin 读书会


<<:  [经典回顾]网路异常疑机房失火,老板:「不是有防火墙?」

>>:  Day 17 To Do List - 切版 2

分散式资料库:New SQL

分散式资料库可以依据资料模型及系统架构分类; OLAP(On-Line Analytical Pro...

Day 12. slate × Interfaces × Data-Model

上一篇我们有提到上图这些画了黄框的 files ,是我们在建立 editor 与操作 editor...

JS 08 - 静态方法

大家好! 我们进入今天的主题吧! 物件方法 如果要推入项目至阵列,我们会使用原型方法。 但是,为什麽...

[Day18]-档案读取

资料夹与档案路径 有关档案路径的模组需要先import os 取得目前工作的资料夹 os.getc...

每日挑战,从Javascript面试题目了解一些你可能忽略的概念 - Day26

tags: ItIron2021 Javascript 前言 昨天我们开始了新的系列,剩下这几天也会...