我一直在嘗試學習如何測驗 akka 型別的 Actor。我一直在網上參考各種例子。我成功運行了示例代碼,但我撰寫簡單單元測驗的努力失敗了。
有人可以指出我做錯了什么嗎?我的目標是能夠撰寫單元測驗來驗證每個行為。
構建.sbt
import Dependencies._
ThisBuild / scalaVersion := "2.13.7"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / organization := "com.example"
ThisBuild / organizationName := "example"
val akkaVersion = "2.6.18"
lazy val root = (project in file("."))
.settings(
name := "akkat",
libraryDependencies = Seq(
scalaTest % Test,
"com.typesafe.akka" %% "akka-actor-typed" % akkaVersion,
"com.typesafe.akka" %% "akka-actor-testkit-typed" % akkaVersion % Test,
"ch.qos.logback" % "logback-classic" % "1.2.3"
)
)
EmotionalFunctionalActor.scala
package example
import akka.actor.typed.{ActorRef, Behavior}
import akka.actor.typed.scaladsl.Behaviors
object EmotionalFunctionalActor {
trait SimpleThing
object EatChocolate extends SimpleThing
object WashDishes extends SimpleThing
object LearnAkka extends SimpleThing
final case class Value(happiness: Int) extends SimpleThing
final case class HowHappy(replyTo: ActorRef[SimpleThing]) extends SimpleThing
def apply(happiness: Int = 0): Behavior[SimpleThing] = Behaviors.receive { (context, message) =>
message match {
case EatChocolate =>
context.log.info(s"($happiness) eating chocolate")
EmotionalFunctionalActor(happiness 1)
case WashDishes =>
context.log.info(s"($happiness) washing dishes, womp womp")
EmotionalFunctionalActor(happiness - 2)
case LearnAkka =>
context.log.info(s"($happiness) Learning Akka, yes!!")
EmotionalFunctionalActor(happiness 100)
case HowHappy(replyTo) =>
replyTo ! Value(happiness)
Behaviors.same
case _ =>
context.log.warn("Received something i don't know")
Behaviors.same
}
}
}
EmoSpec.scala
package example
import akka.actor.testkit.typed.scaladsl.ActorTestKit
import akka.actor.typed.scaladsl.AskPattern.Askable
import akka.util.Timeout
import org.scalatest.BeforeAndAfterAll
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import scala.concurrent.duration.DurationInt
class EmoSpec extends AnyFlatSpec
with BeforeAndAfterAll
with Matchers {
val testKit = ActorTestKit()
override def afterAll(): Unit = testKit.shutdownTestKit()
"Happiness Leve" should "Increase by 1" in {
val emotionActor = testKit.spawn(EmotionalFunctionalActor())
val probe = testKit.createTestProbe[EmotionalFunctionalActor.SimpleThing]()
implicit val timeout: Timeout = 2.second
implicit val sched = testKit.scheduler
import EmotionalFunctionalActor._
emotionActor ! EatChocolate
probe.expectMessage(EatChocolate)
emotionActor ? HowHappy
probe.expectMessage(EmotionalFunctionalActor.Value(1))
val current = probe.expectMessageType[EmotionalFunctionalActor.Value]
current shouldBe 1
}
}
uj5u.com熱心網友回復:
目前還不清楚你遇到了什么問題,所以這個答案有點像在黑暗中拍攝,有一些觀察。
您似乎正在使用“command-then-query”模式來測驗這方面的行為,這沒問題(但請參閱下面的另一種方法,我發現它非常有效)。有兩種基本方法可以解決這個問題,您的測驗看起來像是兩者的混合,可能無法正常作業。
無論采用哪種方法,在向參與者發送初始EatChocolate訊息時:
emotionActor ! EatChocolate
該訊息被發送給actor,而不是probe,因此probe.expectMessage不會成功。
Akka Typed 中有兩種詢問方式。有一個Future基于 - 的演員外部,其中詢問機器注入一個特殊ActorRef的接收回復并回傳一個Future將在收到回復時完成。您可以安排Future將其結果發送到探測器:
val testKit = ActorTestKit()
implicit val ec = testKit.system.executionContext
// after sending EatChocolate
val replyFut: Future[EmotionalFunctionalActor.SimpleThing] = emotionActor ? HowHappy
replyFut.foreach { reply =>
probe.ref ! reply
}
probe.expectMessage(EmotionalFunctionalActor.Value(1))
更簡潔地說,您可以省去Askable、Futures 和 anExecutionContext并將probe.ref其用作訊息中的replyTo欄位HowHappy:
emotionActor ! HowHappy(probe.ref`)
probe.expectMessage(EmotionalFunctionalActor.Value(1))
Future與基于 - 的方法相比,這更簡潔并且可能不會那么不穩定(不太容易出現時序問題) 。相反,由于HowHappy訊息看起來是為與 ask 模式一起使用而設計的,因此Future基于 - 的方法可以更好地實作“測驗作為檔案”的目的,以描述如何與參與者互動。
如果在FutureScalaTest 中使用基于 - 的方法,擴展您的套件可能會很有用akka.actor.testkit.typed.scaladsl.ScalaTestWithActorTestKit:這將提供一些樣板檔案并混合 ScalaTest 的ScalaFutures特征,這將讓您撰寫類似的東西
val replyFut: Future[EmotionalFunctionalActor.SimpleThing] = emotionActor ? HowHappy
assert(replyFut.futureValue == EmotionalFunctionalActor.Value(1))
I tend to prefer the BehaviorTestKit, especially for cases where I'm not testing how two actors interact (this can still be done, it's just a bit laborious with the BehaviorTestKit). This has the advantage of not having any timing at all and generally has less overhead for running tests.
val testKit = BehaviorTestKit(EmotionalFunctionalActor())
val testInbox = TestInbox[EmotionalFunctionalActor.SimpleThing]()
testKit.run(HowHappy(testInbox.ref))
testInbox.expectMessage(EmotionalFunctionalActor.Value(1))
As a side note, when designing a request-response protocol, it's generally a good idea to restrict the response type to the responses that might actually be sent, e.g.:
final case class HowHappy(replyTo: ActorRef[Value]) extends SimpleThing
This ensures that the actor can't reply with anything but a Value message and means that the asker doesn't have to handle any other type of message. If there's a couple of different message types it could respond with, it might be worth having a trait which is only extended (or mixed in) by those responses:
trait HappinessReply
final case class Value(happiness: Int) extends HappinessReply
final case class HowHappy(replyTo: ActorRef[HappinessReply]) extends SimpleThing
Further, the reply often won't make sense as a message received by the sending actor (as indicated in this case by it being handled by the "Received something I don't know" case). In this situation, Value shouldn't extend SimpleThing: it might even just be a bare case class and not extend anything.
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/424400.html
