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
.
- 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.
- è 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. - 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
}