2015-10-18 16 views
10

Quando ho un attore genitore in Akka, che crea direttamente un attore figlio all'inizializzazione, quando voglio scrivere test unitari per l'attore genitore, come posso sostituire l'attore bambino con un TestProbe o un mock?Come schernire i bambini attori per testare un sistema Akka?

Ad esempio, con il seguente codice di esempio artificiosa:

class TopActor extends Actor { 
    val anotherActor = context.actorOf(AnotherActor.props, "anotherActor") 

    override def receive: Receive = { 
    case "call another actor" => anotherActor ! "hello" 
    } 
} 

class AnotherActor extends Actor { 

    override def recieve: Receive = { 
    case "hello" => // do some stuff 
    } 

} 

Se voglio scrivere un test per TopActor, per controllare il messaggio inviato a AnotherActor è "ciao", come faccio a sostituire l'attuazione di AnotherActor? Sembra che TopActor crei direttamente questo figlio, quindi non è facile accedervi.

risposta

8

Il seguente approccio sembra funzionare ma scavalcare la val di un altro attore sembra direttamente un po 'grezzo. Mi chiedevo se ci fossero altri/soluzioni consigliate più pulite, che è il motivo per cui ho ancora fatto la domanda, anche se ho questa risposta di lavoro:

class TopActorSpec extends MyActorTestSuiteTrait { 
    it should "say hello to AnotherActor when receive 'call another actor'" { 
    val testProbe = TestProbe() 

    val testTopActor = TestActorRef(Props(new TopActor { 
     override val anotherActor = testProbe.ref 
    })) 

    testTopActor ! "call another actor" 
    testProbe.expectMsg(500 millis, "hello") 
    } 
} 
+0

Poiché non c'è altra risposta a questa soluzione e ho avuto un upvote, mi sa che sarà accetta la mia risposta :) –

+1

Il tuo 'testTopActor.underlyingActor' ha sia un' anotherActor' che 'TopActor.anotherActor'. Può essere OK se il tuo 'anotherActor' non sta facendo nulla nel suo costruttore o in qualsiasi funzione del ciclo di vita, ma se ci fosse qualcosa in esecuzione, ad es.connessione di rete/database nel costruttore (so che è brutto, ma solo per illustrare il punto), allora avrai 2 operazioni di questo tipo in esecuzione quando crei il tuo 'testTopActor'. Forse è bene stare attenti a questa cosa. – CrazyGreenHand

0

Si consiglia di verificare questa soluzione ho trovato in rete (crediti va a Stig Brautaset): http://www.superloopy.io/articles/2013/injecting-akka-testprobe.html

Si tratta di una soluzione elegante, ma un po 'complicato. Inizia con la creazione di anotherActor tramite un tratto (ChildrenProvider), che puoi avere un productionChildrenProvider che restituisce un'istanza AnotherActor. Durante il test, testChildrenProvider restituirà invece un TestProbe. Guardando il codice di prova, è abbastanza pulito. Ma l'implementazione dell'attore è qualcosa a cui devo pensare.

0

Sono abbastanza nuovo di Scala me stesso. Tuttavia ho affrontato lo stesso problema e mi sono avvicinato come segue. L'idea alla base del mio approccio è quella di iniettare le informazioni su come generare un attore bambino nel genitore corrispondente. Per assicurare un'inizializzazione pulito creo un metodo factory che io uso per instanciate l'attore stesso:

object Parent { 
    def props() :Props { 
    val childSpawner = { 
     (context :ActorContext) => context.actorOf(Child.props()) 
    } 
    Props(classOf[Parent], spawnChild) 
    } 
} 

class Parent(childSpawner: (ActorContext) => ActorRef) extends Actor { 
    val childActor = childSpawner(context) 
    context.watch(childActor) 

    def receive = { 
    // Whatever 
    } 
} 

object Child { 
    def props() = { Props(classOf[Child]) } 
} 

class Child extends Actor { 
    // Definition of Child 
} 

Quindi è possibile testare in questo modo:

// This returns a new actor spawning function regarding the FakeChild 
object FakeChildSpawner{ 
    def spawn(probe :ActorRef) = { 
    (context: ActorContext) => { 
     context.actorOf(Props(new FakeChild(probe))) 
    } 
    } 
} 

// Fake Child forewarding messages to TestProbe 
class FakeChild(probeRef :ActorRef) extends Actor { 
    def receive = { 
    case msg => probeRef ! (msg) 
    } 
} 

"trigger actions of it's children" in { 
    val probe = TestProbe() 

    // Replace logic to spawn Child by logic to spawn FakeChild 
    val actorRef = TestActorRef(
    new Parent(FakeChildSpawner.spawn(probe.ref)) 
) 

    val expectedForewardedMessage = "expected message to child" 
    actorRef ! "message to parent" 

    probe.expectMsg("expected message to child") 
} 

In questo modo si estrae l'azione di deposizione delle uova da il genitore in una funzione anonima che può essere sostituita dall'attore di FakeChild che è completamente nelle tue mani. L'invio di messaggi da FakeChild a TestProbe risolve il problema di test.

Spero che questo aiuti.

1

Forse questa soluzione aiuterà chiunque a risolvere questo problema.

Ho una classe genitore-attore che crea alcuni attori-figlio. L'attore genitore agisce come uno spedizioniere, controlla se il figlio esiste tramite l'id fornito e invia un messaggio ad esso in caso affermativo. In genitore-attore uso context.child(actorId) per verificare se il bambino esiste già. Se voglio verificare come il genitore-attore si comporterà e quali egli manderà al suo bambino che uso qui di seguito il codice:

"ParentActor " should " send XXX message to child actor if he receives YYY message" in { 
    val parentActor = createParentActor(testActor, "child_id") 
    parentActor ! YYY("test_id") 
    expectMsg(XXX) 
} 

def createParentActor(mockedChild: ActorRef, mockedChildId: String): ParentActor = { 
    TestActorRef(new ParentActor(){ 
     override def preStart(): Unit = { 
     context.actorOf(Props(new Forwarder(mockedChild)), mockedChildId) 
     } 
    }) 
    } 

    class Forwarder(target: ActorRef) extends Actor { 
    def receive = { 
     case msg => target forward msg 
    } 
    } 
Problemi correlati