akka-testkit richtig verwenden
19.09.2017
Das Testen von Aktoren unterscheidet sich vom „traditionellen“ Testen von Objekten oder Funktionen. Erstens ist asynchroner Nachrichtenaustausch der einzige Weg, um mit Aktoren zu interagieren. Das bedeutet, dass wir nicht einfach eine Methode oder Funktion aufrufen und dann das tatsächliche Ergebnis mit dem erwarteten vergleichen können. Zweitens – das liegt an der inhärenten Nebenläufigkeit – verhalten sich Aktoren auf nichtdeterministische Art und Weise.
Akka enthält ein Modul namens „akka-testkit“, das beim Testen von Aktoren unterstützt. Es wurde schon vor einiger Zeit erstellt und wir – einschließlich der Autoren – haben seitdem einiges dazugelernt. Wir werden die Werkzeuge vorstellen, die von „akka-testkit“ bereitgestellt werden, und dabei einige Empfehlungen aussprechen, welche wie verwendet werden sollten.
TestActorRef
Akka – genauer gesagt das Modul „akka-actor“ – verhindert, dass wir direkten Zugriff auf eine Instanz eines Aktors erlangen. Jedoch führt „akka-testkit“ die TestActorRef
underlyingActor
Was zunächst wie eine gute Idee erscheint, stellt sich als das genaue Gegenteil heraus, denn die TestActorRef
CurrentThreadDispatcher
Gewöhnlich stellt es kein Problem dar, keinen Zugriff auf eine Instanz eines Aktors zu bekommen, weil es stets möglich sein sollte, die Geschäfts- von der Aktorlogik zu trennen, z.B. indem wir die Geschäftslogik in Form von Methoden im Companion Object definieren. Auf diese Weise können wir die Geschäftslogik immer noch auf „traditionelle“ Art und Weise testen. Und für die Aktorlogik können wir TestProbe
Vermeide die Verwendung von
! TestActorRef
akka-testkit stellt TestKit bereit
Wie oben erwähnt, können wir mit Aktoren nur mittels asynchronem Nachrichtenaustausch interagieren. Daher stellt das Nachrichtenprotokoll – die Nachrichten, die ein Aktor empfängt und versendet – dessen De-facto-API dar. Daher müssen wir spezifische Nachrichten senden und entsprechende Antwortnachrichten erwarten, um die Aktorlogik zu testen.
„akka-testkit“ stellt das TestKit
testActor
ActorRef
TestKit
testActor
Im Folgenden ein einfaches – und zugegebenermaßen etwas konstruiertes – Beispiel, das nicht nur die Verwendung von TestKit
final class EchoSpec extends TestKit(ActorSystem("EchoSpec")) with WordSpecLike with BeforeAndAfterAll { "Echo" should { "respond with 'Hello' upon receiving 'Hello'" in { implicit val sender = testActor val echo = system.actorOf(Echo()) echo ! "Hello" expectMsg("Hello") echo ! "By mistake" } "respond with 'World' upon receiving 'World'" in { implicit val sender = testActor val echo = system.actorOf(Echo()) echo ! "World" expectMsg("World") } } override protected def afterAll() = { ... } } |
Wer kann das Problem erkennen? Richtig, der zweite Test schlägt fehl, weil der testActor
testActor
Darüber hinaus benötigen wir für umfangreichere Protokolle oft mehrere ActorRef
testActor
TestProbe
TestProbe
testActor
Verwende nicht
, sondern test-lokale TestKit
s! TestProbe
TestProbe
TestProbe
TestKit
Im Folgenden das obige einfache Beispiel so umgeschrieben, dass test-lokale TestProbe
final class EchoSpec extends WordSpec with BeforeAndAfterAll { private implicit val system = ActorSystem("EchoSpec") "Echo" should { "respond with 'Hello' upon receiving 'Hello'" in { val sender = TestProbe() implicit val senderRef = sender.ref val echo = system.actorOf(Echo()) echo ! "Hello" sender.expectMsg("Hello") echo ! "This message is sent unintentionally" } "respond with 'World' upon receiving 'World'" in { val sender = TestProbe() implicit val senderRef = sender.ref val echo = system.actorOf(Echo()) echo ! "World" sender.expectMsg("World") } } override protected def afterAll() = { ... } } |
Da jeder Test seine eigene TestProbe
expectMsg
TestProbe
Fazit
Nachdem wir die drei wichtigsten Werkzeuge diskutiert haben, die „akka-testkit“ zur Verfügung stellt, stellen wir fest, dass wir nur TestProbe
TestActorRef
TestKit
Frohes Hacken!