Beliebte Suchanfragen
//

akka-testkit richtig verwenden

18.9.2017 | 4 Minuten Lesezeit

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 TestProbes 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 testActors und werden dann in nachfolgenden Tests anstelle der eigentlichen konsumiert.

Darüber hinaus benötigen wir für umfangreichere Protokolle oft mehrere ActorRefs, 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 TestProbes zu verwenden, wodurch das oben genannte Problem des von allen Tests geteilten testActors gleich vermieden wird.

Verwende nicht TestKit, sondern test-lokale TestProbes!

TestProbe

TestProbes 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 TestProbes 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!

Beitrag teilen

//

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.