问题
I have created a simple test scenario that shows this:
class Test extends AsyncFunSuite {
test("async test") {
val f = Future {
val thread = new Thread {
override def run(): Unit = {
println("OKAYY")
}
}
thread.start()
}
Await.result(f, Duration.Inf)
Future {
assert(true)
}
}
}
When executing this, the test runs forever and never completes.
回答1:
You will see the same behavior with all asynchronous testing in ScalaTest (AsyncFeatureSpec, AsyncFlatSpec, AsyncFreeSpec, AsyncFunSpec, AsyncFunSuite, AsyncWordSpec).
The reason for this is that the default execution context in ScalaTest is serial execution context. You can read more about this here: https://www.scalatest.org/user_guide/async_testing. I have summarized the important points below.
Using ScalaTest's serial execution context on the JVM will ensure the same thread that produced the Future[Assertion] returned from a test body is also used to execute any tasks given to the execution context while executing the test body—and that thread will not be allowed to do anything else until the test completes.
This thread confinement strategy does mean, however, that when you are using the default execution context on the JVM, you must be sure to never block in the test body waiting for a task to be completed by the execution context. If you block, your test will never complete.
Solution 1: Override execution context
implicit override def executionContext = scala.concurrent.ExecutionContext.Implicits.global
Solution 2: Chaining
class Test extends AsyncFunSuite {
test("async test") {
val f = Future {
val thread = new Thread {
override def run(): Unit = {
println("OKAYY")
}
}
thread.start()
}
f.map { _ =>
assert(true)
}
}
}
回答2:
Async style traits provide serial execution context
private final val serialExecutionContext: ExecutionContext = new concurrent.SerialExecutionContext
implicit def executionContext: ExecutionContext = serialExecutionContext
which just puts jobs on a queues and executes them one-by-one using the main thread, that is, there is only a single thread available. Hence blocking this thread at any point will block the whole test suite. Note, however, this choice is by design for the following three reasons
- Performance: running both tests and suites in parallel does not give a significant performance boost compared to just running suites in parallel.
- Thread-safety: if multiple threads are operating in the same suite concurrently, you'll need to make sure access to any mutable fixture objects by multiple threads is synchronized
- Starvation: asynchronous-style tests need not be complete when the test body returns, because the test body returns a Future[Assertion]. This Future[Assertion] will often represent a test that has not yet completed. As a result, when using a more traditional execution context backed by a thread-pool, you could potentially start many more tests executing concurrently than there are threads in the thread pool. The more concurrently execute tests you have competing for threads from the same limited thread pool, the more likely it will be that tests will intermittently fail due to timeouts.
Therefore replacing serial execution context with thread-pool-backed execution context is likely to lead to flakey tests in return for no real gain in performance.
I provide an example and some related information at https://stackoverflow.com/a/55526334/5205022
来源:https://stackoverflow.com/questions/62242989/why-my-scala-async-test-never-completes-when-i-do-await-result