首页 > 解决方案 > 如何使用 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
    }
}

但是验证块中提供的测试无法验证。

如何测试上述包含延迟的功能。

标签: androidunit-testingmockk

解决方案


您的示例还不够完整,无法清楚说明您要做什么,但您的测试似乎:

  1. 启动一个休眠 4 秒的线程,然后调用presenter.navigateToScreen().
  2. 当该线程仍在休眠时,您有一个验证允许 5 秒来满足某些条件
  3. 在验证期间,第一个线程将完成其 4 秒的睡眠 - 还剩 1 秒的验证时间 - 并调用presenter.navigateToScreen()它可能会调用您的function_call()方法。
  4. 你的function_call()方法——最多verify{}剩1秒——会休眠4秒,耗尽verify{}耐心,然后运行// Some code
  5. 由于verify{}可能是在测试完成// Some code后的 3 秒内verify{}完成的操作,因此它永远不会验证。

所以你想从你的测试代码中删除睡眠。如果您的意图是确保调用 sleep(4000),请考虑模拟它,如下所述。

笔记:

  1. 当我测试线程时——如果可以的话——我将线程执行的代码移动到不同的方法或类中,这样我就可以独立于线程处理来测试逻辑。
  2. 当我测试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

推荐阅读