2013-08-21 6 views
6

Ich habe einen Scala-Komponententest für einen Akka-Schauspieler. Der Akteur wurde entwickelt, um ein entferntes System abzufragen und einen lokalen Cache zu aktualisieren. Ein Teil des Designs des Schauspielers besteht darin, dass er nicht versucht, während der Verarbeitung oder des Wartens auf das Ergebnis der letzten Umfrage abzufragen, um zu vermeiden, dass das entfernte System bei einer Verlangsamung überflutet wird.So testen Sie einen Akka-Akteur, der eine Nachricht an sich selbst sendet, ohne Thread.sleep zu verwenden

Ich habe einen Testfall (siehe unten), der Mockito verwendet, um einen langsamen Netzwerkanruf zu simulieren, und überprüft, dass wenn der Schauspieler aufgefordert wird, zu aktualisieren, es keinen weiteren Netzwerkanruf tätigen wird, bis der aktuelle abgeschlossen ist. Es prüft, ob der Akteur einen weiteren Anruf getätigt hat, indem es die fehlenden Interaktionen mit dem Remote-Dienst überprüft.

Ich möchte den Anruf zu Thread.sleep zu beseitigen. Ich möchte die Funktionalität des Aktors testen, ohne darauf angewiesen zu sein, in jedem Testlauf auf eine fest codierte Zeit zu warten, die spröde ist und Zeit kostet. Der Test kann abfragen oder blockieren und auf eine Bedingung mit einer Zeitüberschreitung warten. Dies wird robuster sein und keine Zeit verschwenden, wenn der Test vorüber ist. Ich habe auch die zusätzliche Einschränkung, dass ich den Zustand, der verwendet werden soll, um zusätzliche Abfragen var allowPoll begrenzt im Umfang zu verhindern, zu den Interna der PollingActor.

  1. gibt es eine Möglichkeit erzwingen eine Wartezeit, bis der Schauspieler Nachricht selbst abgeschlossen ist? Wenn es einen Weg gibt, kann ich bis dahin warten, bevor ich versuche zu behaupten.
  2. ist es notwendig, die interne Nachricht überhaupt zu senden? Ich konnte den internen Status nicht mit einer threadsicheren Datenstruktur wie java.util.concurrent.AtomicBoolean verwalten. Ich habe das getan, und der Code scheint zu funktionieren, aber ich weiß nicht genug über Akka, um zu wissen, ob es entmutigt ist - ein Kollege empfahl den Self-Message-Stil.
  3. gibt es bessere, Out-of-the-Box-Funktionalität mit der gleichen Semantik? Dann würde ich mich für einen Integrationstest anstelle eines Komponententests entscheiden, obwohl ich nicht sicher bin, ob es dieses Problem lösen würde.

Der aktuelle Schauspieler sieht ungefähr so ​​aus:

class PollingActor(val remoteService: RemoteServiceThingy) extends ActWhenActiveActor { 

    private var allowPoll: Boolean = true 

    def receive = { 
    case PreventFurtherPolling => { 
     allowPoll = false 
    } 
    case AllowFurtherPolling => { 
     allowPoll = true 
    } 
    case UpdateLocalCache => { 
     if (allowPoll) { 
     self ! PreventFurtherPolling 

     remoteService.makeNetworkCall.onComplete { 
      result => { 
      self ! AllowFurtherPolling 
      // process result 
      } 
     } 
     } 
    } 
    } 
} 

trait RemoteServiceThingy { 
    def makeNetworkCall: Future[String] 
} 

private case object PreventFurtherPolling 
private case object AllowFurtherPolling 

case object UpdateLocalCache 

Und das Gerät zu testen, in specs2, sieht wie folgt aus:

"when request has finished a new requests can be made" ! { 
    val remoteService = mock[RemoteServiceThingy] 
    val actor = TestActorRef(new PollingActor(remoteService)) 

    val slowRequest = new DefaultPromise[String]() 

    remoteService.makeNetworkCall returns slowRequest 

    actor.receive(UpdateLocalCache) 
    actor.receive(UpdateLocalCache) 
    slowRequest.complete(Left(new Exception)) 

    // Although the test calls the actor synchronously, the actor calls *itself* asynchronously, so we must wait. 
    Thread.sleep(1000) 

    actor.receive(UpdateLocalCache) 

    there was two(remoteService).makeNetworkCall 
} 

Antwort

4

Die Art, wie wir gewählt haben, zu lösen Dies ist vorläufig, um dem Akteur das Äquivalent eines Beobachters zu injizieren (Huckepack auf einem existierenden Logger, der nicht in der Liste in der Frage enthalten war). Der Schauspieler kann dem Beobachter dann mitteilen, wenn er aus verschiedenen Zuständen übergegangen ist. Im Testcode führen wir eine Aktion aus und warten dann auf die relevante Benachrichtigung vom Aktor, bevor wir fortfahren und Assertionen machen.

Im Test haben wir etwas wie folgt aus:

actor.receive(UpdateLocalCache) 

observer.doActionThenWaitForEvent(
    { actor.receive(UpdateLocalCache) }, // run this action 
    "IgnoredUpdateLocalCache" // then wait for the actor to emit an event 
} 

// assert on number of calls to remote service 

Ich weiß nicht, ob es eine idiomatische Weg ist, dies scheint wie ein vernünftiger Vorschlag zu mir.

Verwandte Themen