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
ein, die uns doch wieder Zugriff auf die underlyingActor
-Instanz gibt.
Was zunächst wie eine gute Idee erscheint, stellt sich als das genaue Gegenteil heraus, denn die TestActorRef
bringt ihren speziellen Dispatcher – den CurrentThreadDispatcher
– mit, der jegliche Interaktion mit dem Aktor synchron macht. Offensichtlich ist das Testen auf solch einer freundlichen Spielwiese weit von realen Bedingungen entfernt, also Asynchronität und Nichtdeterminismus.
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
s benutzen – ein weiteres Werkzeug, das von „akka-testkit“ bereitgestellt und unten vorgestellt wird.
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
bereit, um davon unsere Testsuiten abzuleiten. So haben wir Zugriff auf einen testActor
, der als (impliziter) Sender oder als gemockte ActorRef
in versendeten Nachrichten verwendet werden kann. Darüber hinaus führt TestKit
eine Vielzahl sogenannter Built-in Assertions ein, die es uns erlauben, Nachrichten zu erwarten, die vom Aktor zum testActor
gesendet werden.
Im Folgenden ein einfaches – und zugegebenermaßen etwas konstruiertes – Beispiel, das nicht nur die Verwendung von TestKit
, sondern auch dessen Kernproblem zeigt:
1final class EchoSpec
2 extends TestKit(ActorSystem("EchoSpec"))
3 with WordSpecLike
4 with BeforeAndAfterAll {
5
6 "Echo" should {
7 "respond with 'Hello' upon receiving 'Hello'" in {
8 implicit val sender = testActor
9 val echo = system.actorOf(Echo())
10 echo ! "Hello"
11 expectMsg("Hello")
12 echo ! "By mistake"
13 }
14
15 "respond with 'World' upon receiving 'World'" in {
16 implicit val sender = testActor
17 val echo = system.actorOf(Echo())
18 echo ! "World"
19 expectMsg("World")
20 }
21 }
22
23 override protected def afterAll() = { ... }
24}
Wer kann das Problem erkennen? Richtig, der zweite Test schlägt fehl, weil der testActor
über alle Tests hinweg geteilt wird. Obwohl Tests gewöhnlich sequentiell ablaufen, verbleiben Nachrichten, die in einem Test gesendet, aber nicht konsumiert werden, in der Mailbox des testActor
s und werden dann in nachfolgenden Tests anstelle der eigentlichen konsumiert.
Darüber hinaus benötigen wir für umfangreichere Protokolle oft mehrere ActorRef
s, die wir wie den testActor
verwenden können. Diese stellt das letzte hier vorgestellte Werkzeug bereit, die TestProbe
. Daher lautet die Empfehlung, immer nur test-lokale TestProbe
s zu verwenden, wodurch das oben genannte Problem des von allen Tests geteilten testActor
s gleich vermieden wird.
Verwende nicht
TestKit
, sondern test-lokaleTestProbe
s!
TestProbe
TestProbe
s sind TestKit
sehr ähnlich – tatsächlich erben sie davon – ohne dabei die Testsuite davon abzuleiten. Stattdessen erzeugen wir sie nach Bedarf für jeden einzelnen Test.
Im Folgenden das obige einfache Beispiel so umgeschrieben, dass test-lokale TestProbe
s verwendet werden:
1final class EchoSpec extends WordSpec with BeforeAndAfterAll {
2
3 private implicit val system = ActorSystem("EchoSpec")
4
5 "Echo" should {
6 "respond with 'Hello' upon receiving 'Hello'" in {
7 val sender = TestProbe()
8 implicit val senderRef = sender.ref
9 val echo = system.actorOf(Echo())
10 echo ! "Hello"
11 sender.expectMsg("Hello")
12 echo ! "This message is sent unintentionally"
13 }
14
15 "respond with 'World' upon receiving 'World'" in {
16 val sender = TestProbe()
17 implicit val senderRef = sender.ref
18 val echo = system.actorOf(Echo())
19 echo ! "World"
20 sender.expectMsg("World")
21 }
22 }
23
24 override protected def afterAll() = { ... }
25}
Da jeder Test seine eigene TestProbe
hat, wird der zweite nicht davon beeinflusst, dass im ersten mehr Nachrichten gesendet als konsumiert werden. Natürlich müssen wir für jeden Test eine Zeile mehr schreiben und expectMsg
und andere Built-in Assertions auf der TestProbe
aufrufen, aber dieser geringe Mehraufwand tut in der Regel nicht weh. Vielmehr wird er sich lohnen, weil Fehler wie die obigen manchmal fast unmöglich zu finden sind.
Fazit
Nachdem wir die drei wichtigsten Werkzeuge diskutiert haben, die „akka-testkit“ zur Verfügung stellt, stellen wir fest, dass wir nur TestProbe
verwenden sollten. Keine TestActorRef
und kein TestKit
.
Frohes Hacken!
Weitere Beiträge
von Heiko Seeberger
Dein Job bei codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
Weitere Artikel in diesem Themenbereich
Entdecke spannende weiterführende Themen und lass dich von der codecentric Welt inspirieren.
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog-Autor*in
Heiko Seeberger
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.
Du hast noch Fragen zu diesem Thema? Dann sprich mich einfach an.