如何确保给定的未来在测试中首先完成?
Posted
技术标签:
【中文标题】如何确保给定的未来在测试中首先完成?【英文标题】:How to make sure a given future completes first in tests? 【发布时间】:2021-06-20 06:27:27 【问题描述】:我正在为函数 bar
编写测试:
def bar(fut1: Future[Int],
fut2: Future[Int],
fut3: Future[Int]): Future[Result] = ???
bar
像这样返回Result
:
case class Result(
x: Int, // fut1 value
oy: Option[Int], // if fut2 is complete then Some of fut2 value else None
oz: Option[Int] // if fut3 is complete then Some of fut3 value else None
)
我想为所有测试用例编写测试:
fut1
已完成,fut2
和 fut3
未完成
fut1
完成,fut2
完成,fut3
未完成
等
所以我正在为这些测试编写函数foo1
、foo2
和foo3
的假 实现。
def foo1(x: Int): Future[Int] = ???
def foo2(x: Int): Future[Int] = ???
def foo3(x: Int): Future[Int] = ???
Test #1 调用所有这些函数,检查 fut1
是否先完成,然后调用 bar
val fut1 = foo1(0)
val fut2 = foo2(0)
val fut3 = foo3(0)
// make sure `fut1` completes first
测试#2 调用所有这些函数,确保fut2
首先完成,然后调用bar
。
测试#3 调用所有这些函数,确保fut3
首先完成,然后调用bar
。
我的问题是如何实现函数foo1
、foo2
和foo3
以及测试。
【问题讨论】:
如果它们是异步的,为什么它们完成的顺序很重要? @ViktorKlang 顺便说一句,非常好的问题,在尝试找到答案之前我应该自己问一下。 @ViktorKlang 这对测试很重要。我想测试三个不同的用例。想象一下,我正在为firstCompletedOf
编写测试。
@ViktorKlang 我更新了问题并意识到您是对的,异步操作的顺序并不重要。谢谢。
不客气,@Michael
【参考方案1】:
您可以尝试通过map
将完整性时间戳附加到每个未来,例如:
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent._
import scala.concurrent.duration._
import scala.language.postfixOps
def foo1(x: Int): Future[Int] = Future Thread.sleep(200); 1
def foo2(x: Int): Future[Int] = Future Thread.sleep(500); 2
def foo3(x: Int): Future[Int] = Future Thread.sleep(500); 3
def completeTs[T](future: Future[T]): Future[(T, Long)] = future.map(v => v -> System.currentTimeMillis())
val resutls = Await.result(Future.sequence(
List(completeTs(foo1(1)), completeTs(foo2(1)), completeTs(foo3(1)))
), 2 seconds)
val firstCompleteTs = resutls.map(_._2).min
val firstCompleteIndex = resutls.indexWhere(_._2 == firstCompleteTs)
assert(firstCompleteIndex == 0)
斯卡蒂:https://scastie.scala-lang.org/L9g78DSNQIm2K1jGlQzXBg
【讨论】:
【参考方案2】:您可以重新利用firstCompletedOf
来验证期货列表中给定指数的期货是否是第一个完成的指数:
import java.util.concurrent.atomic.AtomicReference
import scala.concurrent.ExecutionContext, Future, Promise
import scala.util.Try
def isFirstCompleted[T](idx: Int)(futures: List[Future[T]])(
implicit ec: ExecutionContext): Future[Boolean] =
val promise = Promise[(T, Int)]()
val pRef = new AtomicReference[Promise[(T, Int)]](promise)
futures.zipWithIndex foreach case (f, i) => f onComplete case tt: Try[T] =>
val p = pRef.getAndSet(null)
if (p != null) p tryComplete tt.map((_, i))
promise.future.map case (t, i) => i == idx
试运行:
import scala.concurrent.ExecutionContext.Implicits.global
val futures = List(
FutureThread.sleep(100); 1,
FutureThread.sleep(150); throw new Exception("oops!"),
FutureThread.sleep(50); 3
)
isFirstCompleted(0)(futures) // Future(Success(false))
isFirstCompleted(2)(futures) // Future(Success(true))
要编写测试用例,请考虑使用ScalaTest AsyncFlatSpec
。
【讨论】:
【参考方案3】:目前尚不清楚您要测试的究竟是什么。 如果你只是使用已经完成的期货,你会得到你描述的行为:
def f1 = Future.successful(1)
def f2 = Future.successful(2)
def f3 = Future.successful(3)
eventually
Future.firstCompletedOf(Seq(f1, f2, f3)).value shouldBe Some(1)
(请注意,您不能像您在问题中所做的那样直接与fut1
进行比较,这总是错误的,因为.firstCompletedOf
返回一个新的未来)。
你也可以只完成一个未来,而不要管其他的:
val promise = Promise[Int].future
def f1 = promise.future // or just Future.successful(1) ... or Future(1)
def f2 = Future.never
def f3 = Future.never
result = Future.firstCompletedOf(Seq(f1, f2, f3))
promise.complete(Success(1))
eventually
result.value shouldBe 1
等等......也可以让其他期货也得到他们自己的承诺的支持,例如,如果你希望它们最终都完成(不确定它会给你带来什么,但话又说回来,我不确定你正在这里开始测试)。
另一种可能是让它们相互依赖:
val promise = Promise[Int]
def f1 = promise.future
def f2 = promise.future.map(_ + 1)
def f3 = promise.future.map(_ + 2)
...
promise.complete(Success(1))
【讨论】:
非常感谢!这个答案非常有用。你是对的,不清楚我在测试什么。我会更新问题。 我更新了问题并意识到您的第二个建议确实回答了它。顺序并不重要。我可以用Future.successful
和Future.never
伪造foo1
、foo2
和foo3
。以上是关于如何确保给定的未来在测试中首先完成?的主要内容,如果未能解决你的问题,请参考以下文章