2013-08-21 5 views
6

Ho un test di unità Scala per un attore Akka. L'attore è progettato per eseguire il polling di un sistema remoto e aggiornare una cache locale. Parte del design dell'attore è che non tenta di eseguire il polling mentre è ancora in elaborazione o in attesa del risultato dell'ultimo sondaggio, per evitare di inondare il sistema remoto quando si verifica un rallentamento.Come testare un attore Akka che invia un messaggio a se stesso, senza usare Thread.sleep

Ho un caso di test (illustrato di seguito) che utilizza Mockito per simulare una chiamata di rete lenta e controlla che quando l'attore viene informato di aggiornarsi, non effettuerà un'altra chiamata di rete finché quella corrente non sarà completa. Controlla che l'attore non abbia effettuato un'altra chiamata verificando la mancanza di interazioni con il servizio remoto.

Desidero eliminare la chiamata a Thread.sleep. Voglio testare la funzionalità dell'attore senza fare affidamento sull'attesa di un tempo hardcoded, in ogni esecuzione di test, che è fragile e fa perdere tempo. Il test può eseguire il polling o il blocco, in attesa di una condizione, con un timeout. Questo sarà più robusto e non perderà tempo quando il test sta passando. Inoltre, ho aggiunto il vincolo che voglio mantenere lo stato utilizzato per impedire il polling extra var allowPoll limitato nell'ambito, agli interni di PollingActor.

  1. c'è un modo di forzare un'attesa fino a quando l'attore non ha completato la messaggistica? Se c'è un modo in cui posso aspettare fino ad allora prima di provare ad affermare.
  2. è necessario inviare il messaggio interno? Non è possibile mantenere lo stato interno con una struttura dati thread-safe, ad esempio java.util.concurrent.AtomicBoolean. Ho fatto questo e il codice sembra funzionare, ma non sono abbastanza informato su Akka per sapere se è scoraggiato - un collega ha raccomandato lo stile del messaggio personale.
  3. ci sono funzionalità migliori, pronte all'uso con la stessa semantica? Quindi opterei per un test di integrazione invece di un test unitario, anche se non sono sicuro se risolverebbe questo problema.

L'attore corrente simile a questa:

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 

E il test di unità, in specs2, si presenta così:

"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 
} 

risposta

4

Il modo in cui abbiamo scelto di risolvere questo per ora è quello di iniettare l'equivalente di un osservatore nell'attore (con un piggybacking su un logger esistente che non era incluso nell'elenco nella domanda). L'attore può quindi dire all'osservatore quando è passato da vari stati. Nel codice di test eseguiamo un'azione, quindi attendi la relativa notifica dall'attore, prima di continuare e fare affermazioni.

Nel test abbiamo qualcosa di simile:

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 

Non so se c'è un modo più idiomatico, questa sembra una ragionevole suggerimento a me.