android - 如何使用 mockK 为线程编写单元测试
问题描述
我想使用 mockK 库为以下函数编写单元测试:
void function_name() {
new Thread(() -> {
try {
Thread.sleep(4000);
//some code
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
我尝试使用 mockk 中提供的 Thread:
@Test
fun function_name() {
val bundle = spyk()
Thread {
Thread.sleep(4000)
presenter.navigateToScreen(bundle)
}.start()
verify(timeout = 5000){
//to verify statement
}
}
但是验证块中提供的测试无法验证。
如何测试上述包含延迟的功能。
解决方案
您的示例还不够完整,无法清楚说明您要做什么,但您的测试似乎:
- 启动一个休眠 4 秒的线程,然后调用
presenter.navigateToScreen()
. - 当该线程仍在休眠时,您有一个验证允许 5 秒来满足某些条件
- 在验证期间,第一个线程将完成其 4 秒的睡眠 - 还剩 1 秒的验证时间 - 并调用
presenter.navigateToScreen()
它可能会调用您的function_call()
方法。 - 你的
function_call()
方法——最多verify{}
剩1秒——会休眠4秒,耗尽verify{}
耐心,然后运行// Some code
。 - 由于
verify{}
可能是在测试完成// Some code
后的 3 秒内verify{}
完成的操作,因此它永远不会验证。
所以你想从你的测试代码中删除睡眠。如果您的意图是确保调用 sleep(4000),请考虑模拟它,如下所述。
笔记:
- 当我测试线程时——如果可以的话——我将线程执行的代码移动到不同的方法或类中,这样我就可以独立于线程处理来测试逻辑。
- 当我测试
Thread.sleep()
(或Instant.now()
)之类的东西时,我会注入方法并模拟它们或根据需要验证它们。这意味着测试根本不需要等待任何实时:被测试的代码只需要相信任何被嘲笑sleep()
或now()
合谋让他们相信的东西。
(我也认为拥有名为“timeMachine”和“sleepMachine”的辅助全局事物很有趣,尽管我想它们确实是工厂:它可以节省{ millis -> Thread.sleep(millis) }
多次输入。)
阻止引用,因为代码不喜欢我的 Kotlin:
fun trySeveral( tries: Int, sleepMs: (Long) -> Unit = { millis -> Thread.sleep(millis) }, now: () -> Instant = { Instant.now() } ): Duration? { repeat(tries) { try { val started = now() // some code that throws or doesn't throw return Duration.between(started, now()) } catch (e: InterruptedException) { logger("Failed attempt $it ", e) } sleepMs(200) // Some logic to avoid final sleep } return null }
now()
在这里,我可以通过模拟和/或验证来测试函数sleep()
,这取决于上面注释掉的部分是否抛出:
val sleepMs = mockk<(Long) -> Unit>(relaxed = true) val now = mockk<() -> Instant>() every { now() } returnsMany listOf(1,2,3).map { Instant.ofEpochSecond(it) } trySeveral(3, sleepMs, now) verify(exactly = 2) { // TODO: skip the final sleep sleepMs(200) }
你也可以模拟静态,但我不喜欢这样,因为 <mumble mumble global scope mumble>:
mockkStatic(Thread::class) every { Thread.sleep(any()) } just Runs