Scalatest 异步测试套件与最终和 WhenReady (org.scalatest.concurrent)

Maa*_*mon 6 asynchronous scala future scalatest

我正在尝试使用 scalatest Asynchronous Test Suites,但除了设置超时的一些限制之外,我没有看到测试套件实际添加了什么。

我想知道是否有人精通 scalatest 异步测试,可以快速解释异步测试套件和org.scalatest.concurrent. 异步测试套件实际上添加了org.scalatest.concurrent什么?一种方法比另一种更好吗?

Mar*_*lic 12

我们比较了以下用于测试返回Futures 的代码的 ScalaTest 工具:

异步风格特征

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

Scala期货

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

最终

class EventuallySpec extends FlatSpec with Eventually {
  ...
  eventually { assert(Future(3).value.contains(Success(3))) }
  ...
}
Run Code Online (Sandbox Code Playgroud)
  • 更一般的设施不仅用于 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)
    }
Run Code Online (Sandbox Code Playgroud)

让我们考虑AsyncSpec反对的输出ScalaFuturesSpec

  • 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
    
    Run Code Online (Sandbox Code Playgroud)
  • 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
    
    Run Code Online (Sandbox Code Playgroud)

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

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

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