如何通过 Kotlin 协程在单线程上运行测试?

问题描述 投票:0回答:1

我有课和考试。我想在单个线程上执行所有协程。目前测试失败:

class MainClass {
    val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
    var value = 0

    fun run() {
        scope.launch(Dispatchers.IO) {
            delay(1000)
            withContext(Dispatchers.Main) {
                withContext(Dispatchers.IO) {
                    delay(1000)
                    withContext(Dispatchers.Main) {
                        value = 1
                    }
                }
            }
        }
    }
}
class TestTest {
    @Test
    fun test() = runTest {
        val test = MainClass()
        test.run()
        assertTrue(test.value == 1)
    }
}

如何才能通过测试?

android multithreading kotlin unit-testing kotlin-coroutines
1个回答
0
投票

问题是您在

run
中启动了一个协程,但在测试中您尝试在协程完成之前访问其结果 (
value = 1
)。

这是测试异步代码时的常见问题。解决方案是使用使用“虚拟”时间的 TestDispatcher,这样您就可以让协程以受控的方式执行,例如逐步进行或快进。 这意味着您需要在测试中使用与在 MainClass 中硬编码的调度程序不同的调度程序。这是您应该

注入调度员

的主要原因: class MainClass( val dispatcherIO: CoroutineDispatcher = Dispatchers.IO, ) { val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) var value = 0 fun run() { scope.launch(dispatcherIO) { delay(1000) withContext(Dispatchers.Main) { withContext(dispatcherIO) { delay(1000) withContext(Dispatchers.Main) { value = 1 } } } } } }

此代码的行为与您问题中的代码相同,但测试现在可以提供不同的 IO 调度程序。 
Dispatchers.Main

仍然是硬编码的。其原因是主调度程序在测试中的处理方式略有不同。有一种特殊的方法可以在运行时替换主调度程序,因此只需将 IO 调度程序设为变量即可。

测试现在看起来像这样:

class TestTest { @Test fun test() = runTest { val testDispatcher = StandardTestDispatcher(testScheduler) Dispatchers.setMain(testDispatcher) try { val test = MainClass(testDispatcher) test.run() advanceUntilIdle() assertTrue(test.value == 1) } finally { Dispatchers.resetMain() } } }

testDispatcher

是现在使用的调度程序。首先它被传递到

Dispatchers.setMain()
以取代主调度程序。然后它也被传递给
MainClass
的构造函数,因此它也被用作 IO 调度程序。您可以在这里使用不同的 TestDispatcher,但我认为没有理由为您当前的代码执行此操作。
实际的测试代码现在由一个 try 块包装,之后需要该块来重置主调度程序。不过,通常您会使用 JUnit 测试规则来设置和重置主调度程序,以使测试代码与设置逻辑保持清晰。请参阅此处了解更多信息:

https://developer.android.com/kotlin/coroutines/test#setting-main-dispatcher

到目前为止,更改仅适用于您的测试设置。您需要对实际测试逻辑进行的唯一更改是行

advanceUntilIdle()

:使用

test.run()
启动协程后,现在将暂停当前代码,直到
testScheduler
上的所有协程完成。因此,当您在下一行中测试
test.value == 1
时,这实际上是正确的,并且您的测试成功通过。
    

© www.soinside.com 2019 - 2024. All rights reserved.