【问题标题】:How to specify the exact execution order of asynchronous calls in Scala unit tests?如何在 Scala 单元测试中指定异步调用的确切执行顺序?
【发布时间】:2019-02-27 04:38:59
【问题描述】:

我已经为 Scala 中的 futures 编写了许多不同的单元测试。 所有异步调用都使用执行上下文。 为了确保异步调用始终以相同的顺序执行,我需要延迟一些相当困难的任务并减慢测试速度。 执行者可能仍然(取决于它的实现)在其他任务之前完成一些任务。

测试具有特定执行顺序的并发代码的最佳方法是什么?例如,我有以下测试用例:

"firstSucc" should "complete the final future with the first one" in {
    val executor = getExecutor
    val util = getUtil
    val f0 = util.async(executor, () => 10)
    f0.sync
    val f1 = util.async(executor, () => { delay(); 11 })
    val f = f0.firstSucc(f1)
    f.get should be(10)
  }

其中延迟为def delay() = Thread.sleep(4000)sync 同步未来(调用Await.ready(future, Duration.Inf))。 这就是我想确保 f0 已经完成并且 f1 在 f0 之后完成的方式。完成 f0 是不够的,因为 firstSucc 可能正在洗牌期货。因此,f1应该延迟到f.get检查之后。

另一个想法是从承诺创建期货并在某个时间点完成它们:

  "firstSucc" should "complete the final future with the first one" in {
    val executor = getExecutor
    val util = getUtil
    val f0 = util.async(executor, () => 10)
    val p = getPromise
    val f1 = p.future
    val f = f0.firstSucc(f1)
    f.get should be(10)
    p.trySuccess(11)
  }

有没有更简单/更好的方法来定义执行顺序?也许是另一种可以配置提交任务顺序的执行服务? 对于这种特定情况,将第二个未来延迟到检查结果之后可能就足够了,但在某些情况下,所有未来都必须按特定顺序完成。

完整的代码可以在这里找到:https://github.com/tdauth/scala-futures-promises

测试用例属于此类:https://github.com/tdauth/scala-futures-promises/blob/master/src/test/scala/tdauth/futuresandpromises/AbstractFutureTest.scala

这个问题可能是相关的,因为 Scala 可以使用 Java Executor Services:Controlling Task execution order with ExecutorService

【问题讨论】:

  • 为什么您认为首先测试具有延迟和特定执行顺序的并发代码是一个好主意?并发运行代码中的错误通常恰好发生,因为事件的意外顺序和线程之间的内存同步失败。像Thread.sleep(4000) 这样的东西会使测试几乎毫无用处,因为 JVM 可能会在一段时间后决定同步内存,即使代码中没有任何显式同步,这意味着即使是损坏的代码也可以轻松通过您的测试。
  • @AndreyTyukin “错误是由于意外的顺序而发生的” - 是的,但编写一个测试可能仍然有用,以确保在 特定事件发生的情况下没有错误 顺序
  • @Dima 然后应该将想要测试的逻辑与并发评估策略分开,实现一个单独的顺序确定性解释器,并用它来测试逻辑。只有这样才能切换到并发实现,并测试它是否也按预期工作(无论执行顺序如何,并且没有人为延迟)。首先用并发和期货来实现一切,然后试图施加延迟和特定的顺序,这看起来更像是试图让河流逆重力流动。
  • @AndreyTyukin 是的,没关系。问题是如何测试原始实现在切换到并发后没有中断。
  • 我认为这不是一个好主意,但我需要测试有特定事件顺序的案例。否则,我无法期待某个结果。也许嘲笑未来会有所帮助,我不知道。

标签: scala concurrency future execution


【解决方案1】:

对于大多数简单的情况,我想说一个单线程执行器就足够了 - 如果您一个接一个地启动您的期货,它们将按顺序执行,并以相同的顺序完成。

但看起来您的问题实际上比您所描述的更复杂:您不仅在寻找一种方法来确保一个未来完成晚于另一个,而且总的来说,一系列任意事件以特定顺序发生。例如,您问题中的 sn-p 验证第二个未来 starts 在第一个 completes 之后(我不知道 delay 的用途是什么顺便说一句)。

您可以使用eventually 等待特定事件发生后再继续:

   val f = Future(doSomething) 
   eventually { 
      someFlag shouldBe true
   }
   val f1 = Future(doSomethingElse) 
   eventually { 
      f.isCompleted shouldBe true
   }
   someFlag = false   
   eventually { 
      someFlag shouldBe true
   }

   f1.futureValue shoudlBe false

【讨论】:

  • 谢谢,最终看起来很有趣,但你是对的。由于我将一个未完成的未来传递给 f0.firstSucc(f1) 我需要确保 f0 总是在 f1 之前完成。
  • @Baradé f0.sync 不这样做吗?
  • 确实可以,但是例如,当 firstSucc 对期货进行洗牌时,该解决方案也应该有效。因此,将 f1 延迟到测试之后是很重要的。我添加了另一个带有承诺的解决方案。请注意,问题中的解决方案确实有效,但我正在寻找一种更简洁/更通用的方式来定义顺序。
猜你喜欢
  • 1970-01-01
  • 2012-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-29
  • 2013-03-14
  • 2012-11-14
相关资源
最近更新 更多