【问题标题】:Testing Akka Typed behavior测试 Akka Typed 行为
【发布时间】:2016-03-25 15:43:54
【问题描述】:

如何测试给定行为是否发送了我期望的消息?

比如说,三条某种类型的消息,一个接一个……

对于常规演员(无类型),有来自常规 Akka 的 TestProbe,其方法类似于 expectedMsg:

http://doc.akka.io/api/akka/current/index.html#akka.testkit.TestProbe

使用 akka-typed 我还在摸不着头脑。有一个东西叫EffectfulActorContext,但我不知道怎么用。

示例

假设我正在编写一个简单的PingPong 服务,给定一个数字n 回复Pong(n) n-times。所以:

-> Ping(2)
Pong(2)
Pong(2)
-> Ping(0)
# nothing
-> Ping(1)
Pong(1)

以下是此行为的外观:

case class Ping(i: Int, replyTo: ActorRef[Pong])
case class Pong(i: Int)

val pingPong: Behavior[Ping] = {
    Static {
      case Ping(i, replyTo) => (0 until i.max(0)).map(_=> replyTo ! Pong(i))
    }
  }

我的黑客

现在,由于我不知道如何进行这项工作,所以我现在正在做的“破解”是让演员总是回复一个响应列表。所以行为是:

case class Ping(i: Int, replyTo: ActorRef[List[Pong]])
  case class Pong(i: Int)

  val pingPong: Behavior[Ping] = {
    Static {
      case Ping(i, replyTo) => replyTo ! (0 until i.max(0)).map(_=>Pong(i)).toList
    }
  }

鉴于这个 hacky 更改,测试器很容易编写:

package com.test

import akka.typed.AskPattern._
import akka.typed.ScalaDSL._
import akka.typed.{ActorRef, ActorSystem, Behavior, Props}
import akka.util.Timeout
import com.test.PingPong.{Ping, Pong}
import org.scalatest.{FlatSpec, Matchers}

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}

object PingPongTester {
  /* Expect that the given messages arrived in order */
  def expectMsgs(i: Int, msgs: List[Pong]) = {
    implicit val timeout: Timeout = 5 seconds
    val pingPongBe: ActorSystem[Ping] = ActorSystem("pingPongTester", Props(PingPong.pingPong))

    val futures: Future[List[Pong]] = pingPongBe ? (Ping(i, _))
    for {
      pongs <- futures
      done <- {
        for ((actual, expected) <- pongs.zip(msgs)) {
          assert(actual == expected, s"Expected $expected, but received $actual")
        }
        assert(pongs.size == msgs.size, s"Expected ${msgs.size} messages, but received ${pongs.size}")
        pingPongBe.terminate
      }
    } Await.ready(pingPongBe.whenTerminated, 5 seconds)
  }
}


object PingPong {
  case class Ping(i: Int, replyTo: ActorRef[List[Pong]])
  case class Pong(i: Int)

  val pingPong: Behavior[Ping] = {
    Static {
      case Ping(i, replyTo) => replyTo ! (0 until i.max(0)).map(_=>Pong(i)).toList
    }
  }
}

class MainSpec extends FlatSpec with Matchers {
  "PingPong" should "reply with empty when Pinged with zero" in {
    PingPongTester.expectMsgs(0, List.empty)
  }
  it should "reply once when Pinged with one" in {
    PingPongTester.expectMsgs(1, List(Pong(1)))
  }
  it should "reply with empty when Pinged with negative" in {
    PingPongTester.expectMsgs(-1, List.empty)
  }
  it should "reply with as many pongs as Ping requested" in {
    PingPongTester.expectMsgs(5, List(Pong(5), Pong(5), Pong(5), Pong(5), Pong(5)))
  }
}

【问题讨论】:

    标签: scala akka akka-typed


    【解决方案1】:

    我最初的测试方法是扩展 Behavior 类

      class TestQueueBehavior[Protocol] extends Behavior[Protocol] {
        val messages: BlockingQueue[Protocol] = new LinkedBlockingQueue[Protocol]()
    
        val behavior: Protocol => Unit = {
          (p: Protocol) => messages.put(p)
        }
    
        def pollMessage(timeout: FiniteDuration = 3.seconds): Protocol = {
          messages.poll(timeout.toMillis, TimeUnit.MILLISECONDS)
        }
    
        override def management(ctx: ActorContext[Protocol], msg: Signal): Behavior[Protocol] = msg match {
          case _ ⇒ ScalaDSL.Unhandled
        }
    
        override def message(ctx: ActorContext[Protocol], msg: Protocol): Behavior[Protocol] = msg match {
          case p =>
            behavior(p)
            Same
        }
      }
    

    然后我可以调用behavior.pollMessage(2.seconds) shouldBe somethingToCompareTo,这与使用TestProbe非常相似。

    虽然我认为EffectfulActorContext 是正确的方法,但遗憾的是无法弄清楚如何正确使用它。

    【讨论】:

      【解决方案2】:

      我正在使用 EffectfulActorContext 来测试我的 Akka 类型演员,这是一个基于您的问题的未经测试的示例。

      注意:我也在使用 Akka 类型测试用例中提供的 guardianactor。

      class Test extends TypedSpec{
          val system = ActorSystem("actor-system", Props(guardian()))
          val ctx: EffectfulActorContext[Ping] = new EffectfulActorContext[Ping]("ping", Ping.props(), system)
      
          //This will send the command to Ping Actor
          ctx.run(Ping)
          //This should get you the inbox of the Pong created inside the Ping actor. 
          val pongInbox = ctx.getInbox("pong")
          assert(pongInbox.hasMessages)
          val pongMessages = pongInbox.receiveAll()
          pongMessages.size should be(1) //1 or whatever number of messages you expect
        }
      

      编辑(更多信息):在我需要在消息中添加replyTo ActorRef 的情况下,我会执行以下操作:

      case class Pong(replyTo: ActorRef[Response])
      val responseInbox: SyncInbox[Response] = Inbox.sync[Response]("responseInbox")
      Pong(responseInbox.ref) 
      

      【讨论】:

      • 不错。 receiveAll 是做什么的?会等吗?
      • 我更新了代码,因为它引用了我的一些代码,并修复了我使用ctx.getInbox("pong") 访问Pong 收件箱的方式。您可以检查编辑。致电receiveAll 会为您提供Pong 收件箱中的所有消息,我不确定它是否会等待,但我会调查一下。
      猜你喜欢
      • 2020-12-20
      • 2019-12-19
      • 2020-12-07
      • 2020-05-31
      • 2021-01-30
      • 2021-06-06
      • 2021-06-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多