Scalatest异步测试套件与最终和何时就绪(org.scalatest.concurrent)

时间:2019-04-03 05:44:54

标签: scalatest

我正在尝试使用scalatest异步测试套件,但是除了设置超时方面有一些限制之外,我没有看到测试套件实际上添加了什么。

我想知道是否有人精通scalatest进行异步测试,可以快速解释异步测试套件和org.scalatest.concurrent之间的区别。那是测试套件实际上添加了org.scalatest.concurrent吗?否则,最好是用一种方法代替另一种方法

1 个答案:

答案 0 :(得分:2)

我们比较以下ScalaTest工具来测试返回Future s的代码:

异步样式特征

class AsyncSpec extends AsyncFlatSpec {
  ...
  Future(3).map { v => assert(v == 3) }
  ...
}
  • 非阻塞
  • 我们可以在Future完成之前断言,即返回Future[Assertion]而不是Assertion
  • 线程安全
  • 单线程串行执行上下文
  • Futures按照启动顺序并依次执行并完成
  • 用于将任务排入测试主体中的同一线程也用于随后执行任务
  • 断言可以映射到Futures
  • 无需在测试体内进行拦截,即使用AwaitwhenReady
  • 消除由于线程饥饿而造成的剥脱
  • 测试主体中的最后一个表达式必须为Future[Assertion]
  • 在测试主体中不支持多个断言
  • 不能在测试体内使用阻塞构造,因为它会由于等待而永久挂起测试 入队但从未开始任务

ScalaFutures

class ScalaFuturesSpec extends FlatSpec with ScalaFutures {
  ...
  whenReady(Future(3) { v => assert(v == 3) }
  ...
}
  • 阻止
  • 我们必须等待完成Future才能返回Assertion
  • 不是线程安全的
  • 可能与全局执行上下文scala.concurrent.ExecutionContext.Implicits.global一起使用,它是一个 多线程并行执行池
  • 在同一测试正文中支持多个断言
  • 测试主体中的最后一个表达式不必是Assertion

最终

class EventuallySpec extends FlatSpec with Eventually {
  ...
  eventually { assert(Future(3).value.contains(Success(3))) }
  ...
}
  • 更多通用功能,不仅用于Futures
  • 这里的语义是重试按名字传递的任何类型的代码块,直到满足断言为止
  • 在测试Futures时,很有可能会使用全局执行上下文
  • 主要用于集成测试,其中针对具有不可预测响应时间的真实服务进行测试

单线程串行执行模型与线程池全局执行模型

scalatest-async-testing-comparison是一个示例 展示了两种执行模型的差异。

给出以下测试体

    val f1 = Future {
      val tmp = mutableSharedState
      Thread.sleep(5000)
      println(s"Start Future1 with mutableSharedState=$tmp in thread=${Thread.currentThread}")
      mutableSharedState = tmp + 1
      println(s"Complete Future1 with mutableSharedState=$mutableSharedState")
    }

    val f2 = Future {
      val tmp = mutableSharedState
      println(s"Start Future2 with mutableSharedState=$tmp in thread=${Thread.currentThread}")
      mutableSharedState = tmp + 1
      println(s"Complete Future2 with mutableSharedState=$mutableSharedState")
    }

    for {
      _ <- f1
      _ <- f2
    } yield {
      assert(mutableSharedState == 2)
    }

让我们考虑AsyncSpecScalaFuturesSpec的输出

  • testOnly示例。AsyncSpec:

    Start Future1 with mutableSharedState=0 in thread=Thread[pool-11-thread-3-ScalaTest-running-AsyncSpec,5,main]
    Complete Future1 with mutableSharedState=1
    Start Future2 with mutableSharedState=1 in thread=Thread[pool-11-thread-3-ScalaTest-running-AsyncSpec,5,main]
    Complete Future2 with mutableSharedState=2
    
  • testOnly示例。ScalaFuturesSpec:

    Start Future2 with mutableSharedState=0 in thread=Thread[scala-execution-context-global-119,5,main]
    Complete Future2 with mutableSharedState=1
    Start Future1 with mutableSharedState=0 in thread=Thread[scala-execution-context-global-120,5,main]
    Complete Future1 with mutableSharedState=1
    

请注意在串行执行模型中如何使用相同的线程以及如何按顺序完成期货。另一方面, 在全局执行模型中,使用了不同的线程,并且在Future2之前完成了Future1,这导致 共享可变状态下的竞争状态,这又导致测试失败。

我们应该使用哪个(IMO)?

在单元测试中,我们应该使用模拟子系统,其中返回的Futures应该立即完成,因此 在单元测试中不需要Eventually。因此,选择是在异步样式和ScalaFutures之间。主要区别 两者之间的区别在于前者与后者不同。如果可能的话,我们绝不应该阻止,所以我们 应该更喜欢AsyncFlatSpec之类的异步样式。执行模型还有更大的不同。异步样式 默认情况下,使用自定义串行执行模型,该模型在共享的可变状态下提供线程安全,这与全局 线程池支持的执行模型通常与ScalaFutures一起使用。总之,我的建议是我们使用异步样式 特质,除非我们有充分的理由不这样做。