将期货与参与者消息混合时,确保测试中的消息顺序 [英] Ensure message order in test when mixing futures with actor messages

查看:75
本文介绍了将期货与参与者消息混合时,确保测试中的消息顺序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在测试一个使用基于未来异步API的演员。当将来完成时,参与者使用管道模式向自身发送消息:

I'm testing an actor that uses an asnychronous future-based API. The actor uses the pipe pattern to send a message to itself when a future completes:

import akka.pattern.pipe
// ...

// somewhere in the actor's receive method
futureBasedApi.doSomething().pipeTo(self)

在我的测试中,我模拟了API,因此我通过promise来控制将来的完成。但是,这与直接发送给actor的其他消息交织在一起:

In my test I mock the API so I control future completion via promises. However, this is interleaved with other messages sent directly to the actor:

myActor ! Message("A")
promiseFromApiCall.success(Message("B"))
myActor ! Message("C")

现在我想知道如何保证演员收到和在我的测试中,b $ b处理消息A和C之间的消息B,因为消息B实际上是在另一个线程中发送的,所以我无法控制演员邮箱接收消息的顺序

Now I'm wondering how I can guarantee that the actor receives and processes message B between message A and C in my test because message B is actually sent in another thread, so I can't control the order in which the actor's mailbox receives the messages.

我考虑了几种可能的解决方案:

I thought about several possible solutions:


  • 每个消息几秒钟后就进入睡眠状态再次发出
    订单的可能性很小

  • sleep after each message for a few milliseconds to make another order very unlikely

等待演员确认每条消息,尽管仅
的确认才需要测试

wait for the actor to acknowledge each message, although acknowledgement is only required for testing

直接将消息B发送给演员,以模拟
将来的完成情况,并编写单独的测试以确保管道模式
正确使用(如果参与者将
不将结果消息传递到自身,则上面的测试不会失败)

send message B directly to the actor to simulate completion of the future and write a separate test that ensures that the pipe pattern is properly used (the test above would not fail if the actor would not pipe the result message to itself)

我不太喜欢这两个选项,但我倾向于使用最后
个。还有其他更好的方法可以在测试中强制执行特定的消息顺序吗?

I don't really like either of these options but I tend to use the last one. Is there another better way I can enforce a certain message order in the tests?

说明:问题不在于如何处理这一事实。在生产中可能会以随机顺序接收到消息。在测试中控制顺序对于确保角色可以实际处理不同的消息顺序至关重要。

Clarification: The question is not how to deal with the fact that messages might be received in random order in production. Controlling the order in the test is essential to make sure that the actor can actually deal with different message orders.

推荐答案

关于akka的更多信息,我终于找到了一个更好的解决方案:用我可以在测试中观察到的邮箱替换actor邮箱。这样,我可以等到演员完成诺言后,直到演员收到新消息。然后才发送下一条消息。 TestingMailbox 的代码在帖子末尾给出。

After reading a lot more about akka, I finally found a better solution: Replacing the actor mailbox with one I can observe in the tests. This way I can wait until the actor receives a new message after I complete the promise. Only then the next message is sent. The code for this TestingMailbox is given at the end of the post.

更新:在Akka Typed中,可以使用 BehaviorInterceptor 非常优雅地实现。只需使用自定义拦截器包装正在测试的 Behavior 即可,该拦截器转发所有消息和信号,但让您观察它们。
下面给出了无类型Akka的邮箱解决方案。

Update: In Akka Typed this can be achieved very elegantly with a BehaviorInterceptor. Just wrap the Behavior under test with a custom interceptor that forwards all messages and signals but lets you observe them. The mailbox solution for untyped Akka is given below.

可以将actor配置为:

The actor can be configured like this:

actorUnderTest = system.actorOf(Props[MyActor]).withMailbox("testing-mailbox"))

我必须通过提供配置来确保actor系统知道测试邮箱:

I have to make sure the "testing-mailbox" is known by the actor system by providing a configuration:

class MyTest extends TestKit(ActorSystem("some name",
    ConfigFactory.parseString("""{ 
        testing-mailbox = {
            mailbox-type = "my.package.TestingMailbox" 
        }
    }"""))) 
    with BeforeAndAfterAll // ... and so on

设置好之后,我可以这样更改测试:

With this being set up, I can change my test like this:

myActor ! Message("A")
val nextMessage = TestingMailbox.nextMessage(actorUnderTest)
promiseFromApiCall.success(Message("B"))
Await.ready(nextMessage, 3.seconds)
myActor ! Message("C")

使用一些辅助方法,我什至可以这样写:

With a little helper method, I can even write it like this:

myActor ! Message("A")
receiveMessageAfter { promiseFromApiCall.success(Message("B")) }
myActor ! Message("C")

这是我的自定义邮箱:

import akka.actor.{ActorRef, ActorSystem}
import akka.dispatch._
import com.typesafe.config.Config 
import scala.concurrent.{Future, Promise}

object TestingMailbox {

  val promisesByReceiver =
    scala.collection.concurrent.TrieMap[ActorRef, Promise[Any]]()

  class MessageQueue extends UnboundedMailbox.MessageQueue {

    override def enqueue(receiver: ActorRef, handle: Envelope): Unit = {
      super.enqueue(receiver, handle)
      promisesByReceiver.remove(receiver).foreach(_.success(handle.message))
    }

  }

  def nextMessage(receiver: ActorRef): Future[Any] =
    promisesByReceiver.getOrElseUpdate(receiver, Promise[Any]).future

}

class TestingMailbox extends MailboxType
  with ProducesMessageQueue[TestingMailbox.MessageQueue] {

  import TestingMailbox._

  def this(settings: ActorSystem.Settings, config: Config) = this()

  final override def create(owner: Option[ActorRef],
                            system: Option[ActorSystem]) =
      new MessageQueue()

}

这篇关于将期货与参与者消息混合时,确保测试中的消息顺序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆