Scalatest 异步测试套件与 Eventually 和 WhenReady (org.scalatest.concurrent)
Scalatest Asynchronous Test Suites vs Eventually and WhenReady (org.scalatest.concurrent)
我正在尝试使用 scalatest Asynchronous Test Suites,但除了对设置超时的一些限制之外,我看不到测试套件实际添加了什么。
我想知道是否有人精通 scalatest 的异步测试可以快速解释异步测试套件和 org.scalatest.concurrent
之间的区别。异步测试套件实际上添加了什么 org.scalatest.concurrent
?一种方法比另一种更好吗?
我们比较了以下用于测试代码的 ScalaTest 工具 returns Future
s:
- Asynchronous style traits, for example,
AsyncFlatSpec
- ScalaFutures
- Eventually
异步风格特征
class AsyncSpec extends AsyncFlatSpec {
...
Future(3).map { v => assert(v == 3) }
...
}
- 非阻塞
- 我们可以在
Future
完成之前断言,即 return Future[Assertion]
而不是 Assertion
- 线程安全
- 单线程串行执行上下文
Futures
按照开始的顺序依次执行完成
- 用于在测试主体中排队任务的同一线程也用于之后执行它们
- 断言可以映射到
Futures
- 无需在测试体内进行阻塞,即使用
Await
、whenReady
- 消除了由于线程不足造成的片状问题
- 测试主体中的最后一个表达式必须是
Future[Assertion]
- 不支持测试主体中的多个断言
- 不能在测试体内使用阻塞结构,因为它会因为等待而永远挂起测试
排队但从未开始任务
ScalaFutures
class ScalaFuturesSpec extends FlatSpec with ScalaFutures {
...
whenReady(Future(3) { v => assert(v == 3) }
...
}
- 阻塞
- 我们必须等到完成
Future
才能 return 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)
}
让我们考虑 AsyncSpec
对 ScalaFuturesSpec
的输出
仅测试example.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
仅测试example.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
请注意在串行执行模型中如何使用相同的线程并且 Futures 按顺序完成。另一方面,
在全局执行模型中使用了不同的线程,并且 Future2
在 Future1
之前完成,这导致
共享可变状态的竞争条件,这反过来又导致测试失败。
我们应该使用哪一个 (IMO)?
在单元测试中,我们应该使用模拟子系统,其中 returned Futures
应该几乎立即完成,所以有
在单元测试中不需要 Eventually
。因此,选择是在异步样式和 ScalaFutures
之间。主要区别
两者之间的区别在于前者与后者不同是非阻塞的。如果可能的话,我们不应该阻止,所以我们
应该更喜欢像 AsyncFlatSpec
这样的异步样式。更大的区别是执行模型。异步样式
默认情况下使用自定义串行执行模型,它在共享可变状态上提供线程安全,与全局不同
线程池支持的执行模型通常与 ScalaFutures
一起使用。总之,我的建议是我们使用异步风格
除非我们有充分的理由不这样做。
我正在尝试使用 scalatest Asynchronous Test Suites,但除了对设置超时的一些限制之外,我看不到测试套件实际添加了什么。
我想知道是否有人精通 scalatest 的异步测试可以快速解释异步测试套件和 org.scalatest.concurrent
之间的区别。异步测试套件实际上添加了什么 org.scalatest.concurrent
?一种方法比另一种更好吗?
我们比较了以下用于测试代码的 ScalaTest 工具 returns Future
s:
- Asynchronous style traits, for example,
AsyncFlatSpec
- ScalaFutures
- Eventually
异步风格特征
class AsyncSpec extends AsyncFlatSpec {
...
Future(3).map { v => assert(v == 3) }
...
}
- 非阻塞
- 我们可以在
Future
完成之前断言,即 returnFuture[Assertion]
而不是Assertion
- 线程安全
- 单线程串行执行上下文
Futures
按照开始的顺序依次执行完成- 用于在测试主体中排队任务的同一线程也用于之后执行它们
- 断言可以映射到
Futures
- 无需在测试体内进行阻塞,即使用
Await
、whenReady
- 消除了由于线程不足造成的片状问题
- 测试主体中的最后一个表达式必须是
Future[Assertion]
- 不支持测试主体中的多个断言
- 不能在测试体内使用阻塞结构,因为它会因为等待而永远挂起测试 排队但从未开始任务
ScalaFutures
class ScalaFuturesSpec extends FlatSpec with ScalaFutures {
...
whenReady(Future(3) { v => assert(v == 3) }
...
}
- 阻塞
- 我们必须等到完成
Future
才能 returnAssertion
- 不是线程安全的
- 可能与全局执行上下文一起使用
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)
}
让我们考虑 AsyncSpec
对 ScalaFuturesSpec
仅测试example.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
仅测试example.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
请注意在串行执行模型中如何使用相同的线程并且 Futures 按顺序完成。另一方面,
在全局执行模型中使用了不同的线程,并且 Future2
在 Future1
之前完成,这导致
共享可变状态的竞争条件,这反过来又导致测试失败。
我们应该使用哪一个 (IMO)?
在单元测试中,我们应该使用模拟子系统,其中 returned Futures
应该几乎立即完成,所以有
在单元测试中不需要 Eventually
。因此,选择是在异步样式和 ScalaFutures
之间。主要区别
两者之间的区别在于前者与后者不同是非阻塞的。如果可能的话,我们不应该阻止,所以我们
应该更喜欢像 AsyncFlatSpec
这样的异步样式。更大的区别是执行模型。异步样式
默认情况下使用自定义串行执行模型,它在共享可变状态上提供线程安全,与全局不同
线程池支持的执行模型通常与 ScalaFutures
一起使用。总之,我的建议是我们使用异步风格
除非我们有充分的理由不这样做。