Akkaアクターのテスト
1. 概要
Akkaは、JVM上でリアクティブ、並行、および分散アプリケーションを構築するための便利なフレームワークまたはツールキットです。 これはリアクティブマニフェストに基づいているため、イベント駆動型で、復元力があり、スケーラブルで、応答性があります。
アクターは、Akkaの基本的な構成要素であり、メッセージを交換することによってのみ通信できる、メモリフットプリントが小さい小さな処理ユニットを表します。
このチュートリアルでは、アクターをテストして、期待どおりに動作することを確認する方法を見ていきます。
2. テスト構成
Akkaチームが推奨する通常のクラシックActorsの代わりに、Akkaタイプを使用します。
テストスイートを設定するには、テストの依存関係を build.sbt:に追加する必要があります。
val AkkaVersion = "2.6.16"
libraryDependencies += "com.typesafe.akka" %% "akka-actor-testkit-typed" % AkkaVersion % Test
libraryDependencies += "org.scalatest" %% "scalatest" % "3.1.4" % Test
AkkaTestKit は、Akkaチームが推奨するScalaTestと一緒に使用するのが最適です。
AkkaTestKit は、以下へのアクセスを提供するActorTestKitのインスタンスを作成します。
- ActorSystem
- アクターを生成するためのメソッド。
- テストスイートからActorSystemをシャットダウンする方法
テストキットを提供する簡単なテスト構成は次のとおりです。
class TestService
extends AnyWordSpec
with BeforeAndAfterAll
with Matchers {
val testKit = ActorTestKit()
implicit val system = testKit.system
override def afterAll(): Unit = testKit.shutdownTestKit()
}
これで、クラスを拡張してテストの作成を開始できます。 また、ActorSystemのシャットダウンを担当するafterAllメソッドをオーバーライドする必要がありました。
3. テストパターン
あいさつを送信する単純なあいさつActorプログラムを設計し、Actorが次のように単純に応答するとします。
object Greeter {
case class Sent(greeting: String, self: ActorRef[Received])
case class Received(greeting: String)
def apply(): Behavior[Sent] = Behaviors.receiveMessage {
case Sent(greeting, recipient) =>
recipient ! Received(greeting)
Behaviors.same
}
}
testkitを使用してこのActorを生成し、送信したのと同じメッセージを受信することを確認することで、この単純なプログラムをテストできます。
testkit は、テストプローブを提供します。 このプローブは、テスト中のActorからメッセージを受信します。 このプローブは、不要なメッセージが入らないようにします。
class GreeterTest extends TestService {
import scala.concurrent.duration._
val greeting = "Hello there"
val sender = testKit.spawn(Greeter(), "greeter")
val probe = testKit.createTestProbe[Greeter.GreetingResponse]()
sender ! Greeter.GreetingRequest(greeting, probe.ref)
probe.expectMessage(Greeter.GreetingResponse(greeting))
probe.expectNoMessage(50 millis)
}
ここでは、入力された Actor を表すプローブを使用して、予期されたメッセージだけを確実に受信できるようにします。
テストプローブは基本的に、 Actor、の代わりに使用できるクエリ可能なメールボックスであり、受信したメッセージをアサートできます。
より複雑な例として、 Actor を使用して信号機システムを実装し、状態が常に保持されて正しいことを確認しましょう。
object TrafficLight {
sealed trait Signal
object Signal {
case object RED extends Signal
case object YELLOW extends Signal
case object GREEN extends Signal
}
sealed trait SignalCommand
object SignalCommand {
case class ChangeSignal(recipient : ActorRef[CurrentSignal]) extends SignalCommand
case class GetSignal (recipient : ActorRef[CurrentSignal]) extends SignalCommand
}
case class CurrentSignal(signal : Signal)
import Signal._
def apply() : Behavior[SignalCommand] = Behaviors.setup{_ =>
var state : Signal = RED
Behaviors.receiveMessage {
case ChangeSignal(recipient) =>
val nextState = state match {
case RED => YELLOW
case YELLOW => GREEN
case GREEN => RED
}
state = nextState
recipient ! CurrentSignal(nextState)
Behaviors.same
case GetSignal(recipient) =>
recipient ! CurrentSignal(state)
Behaviors.same
}
}
}
この単純なActorでは、初期状態は REDです。受信したメッセージに応じて、状態を変更したり、現在の状態を返したりできます。 このActorをテストして、期待どおりに状態が変化することを確認する必要があります。 また、後で他のメッセージを受信しないことをテストする必要があります。
class TrafficLightTest extends TestService {
import scala.concurrent.duration._
val sender = testKit.spawn(TrafficLight(), "traffic")
val probe = testKit.createTestProbe[TrafficLight.CurrentSignal]()
sender ! TrafficLight.SignalCommand.GetSignal(probe.ref)
probe.expectMessage(TrafficLight.CurrentSignal(TrafficLight.Signal.RED))
probe.expectNoMessage(50 millis)
sender ! TrafficLight.SignalCommand.ChangeSignal(probe.ref)
probe.expectMessage(TrafficLight.CurrentSignal(TrafficLight.Signal.YELLOW))
probe.expectNoMessage(50 millis)
sender ! TrafficLight.SignalCommand.ChangeSignal(probe.ref)
probe.expectMessage(TrafficLight.CurrentSignal(TrafficLight.Signal.GREEN))
probe.expectNoMessage(50 millis)
sender ! TrafficLight.SignalCommand.ChangeSignal(probe.ref)
probe.expectMessage(TrafficLight.CurrentSignal(TrafficLight.Signal.RED))
probe.expectNoMessage(50 millis)
}
このテストでは、Actorが期待どおりに状態を変更していることがわかります。
アクター:で質問パターンをテストすることもできます
class TrafficLightTestFut extends TestService {
import scala.concurrent.duration._
val sender = testKit.spawn(TrafficLight(), "traffic")
val duration = 300 millis
implicit val timeout = Timeout(duration)
val signalFut = sender.ask( replyTo => TrafficLight.SignalCommand.GetSignal(replyTo) )
val signal = Await.result(signalFut, duration)
assert(signal == CurrentSignal(RED))
}
トラフィックActorは現在の信号を照会され、正しい信号を返します。
4. 結論
この記事では、型指定された Actor テスト構成をセットアップする方法と、単純なテストパターンを記述して観察する方法について説明しました。 テストを作成する方法はたくさんあります。テストがどのように機能するかについての基本的な基礎と構成要素を理解する必要があります。
いつものように、ソースコードはGitHubのにあります。