您的示例还不够完整,无法清楚说明您要做什么,但您的测试似乎:
- 启动一个休眠 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