2013-05-14 19 views
45

Questa è una domanda di carattere generale relativa ai bulloni di test unità e ai beccucci in una topologia Storm scritta in Java.Test di bulloni e becchi di sfiato

Qual è la pratica raccomandata e linee guida per l'unità-test (JUnit?) Bulloni e beccucci?

Per esempio, potrei scrivere un test JUnit per un Bolt, ma senza comprendere appieno il quadro (come il ciclo di vita di un Bolt) e le implicazioni di serializzazione, facilmente fare l'errore di creazione Costruttore a base di non-serializzabile variabili membro. In JUnit, questo test sarebbe passato, ma in una topologia, non avrebbe funzionato. Immagino che ci siano molti punti di prova da considerare (come questo esempio con Serialization & lifecycle).

Pertanto, è consigliabile che se si utilizza unit test JUnit base, si esegue una piccola topologia finto (LocalMode?) E testare il contratto implicito per il Bolt (o Spout) sotto quella della topologia? Oppure, è GIUSTO usare JUnit, ma l'implicazione è che dobbiamo simulare il ciclo di vita di un Bolt (crearlo, chiamare prepare(), deridere uno Config, ecc.) Attentamente? In questo caso, quali sono i punti di test generali per la classe in esame (Bolt/Spout) da considerare?

Cosa hanno fatto altri sviluppatori per quanto riguarda la creazione di test di unità adeguati?

Ho notato che esiste un'API di test della topologia (vedere: https://github.com/xumingming/storm-lib/blob/master/src/jvm/storm/TestingApiDemo.java). È meglio utilizzare alcune di queste API e alzare "Topologie di test" per ogni singolo Bolt & Spout (e verificare il contratto implicito che il bullone deve prevedere, ad esempio: le uscite dichiarate)?

Grazie

+0

Hai mai decidere su un approccio? –

+0

Bene, ho letto le risposte qui sotto. Sembra che ci siano delle linee guida generali, ma niente di concreto. Ho intenzione di lasciare la domanda aperta per un po 'più a lungo per vedere se qualcun altro ha qualche pensiero quindi chiuderlo. Mi piacciono entrambi gli approcci all'utilizzo dell'API di test (TestingApiDemo.java) nonché la risposta per deridere le dipendenze, @ChrisGerken. – Jack

risposta

8

Il nostro approccio consiste nell'utilizzare l'iniezione del costruttore di una fabbrica serializzabile nel beccuccio/chiavistello. Il beccuccio/bullone quindi consulta la fabbrica nel suo metodo aperto/di preparazione.L'unica responsabilità della fabbrica è quella di incapsulare ottenendo le dipendenze del beccuccio/chiavistello in modo serializzabile. Questo design consente ai nostri test unitari di iniettare fabbriche finte/test/fittizie che, quando consultate, restituiscono servizi di simulazione. In questo modo possiamo testare in modo restrittivo il beccuccio/i bulloni usando i mock, ad es. Mockito.

Di seguito è un esempio generico di un bullone e un test per questo. Ho omesso l'implementazione della fabbrica UserNotificationFactory perché dipende dalla tua applicazione. È possibile utilizzare i localizzatori di servizio per ottenere i servizi, la configurazione serializzata, la configurazione accessibile tramite HDFS o in ogni modo per ottenere i servizi corretti, purché la fabbrica possa farlo dopo un ciclo di sicurezza. Dovresti coprire la serializzazione di quella classe.

Bolt

public class NotifyUserBolt extends BaseBasicBolt { 
    public static final String NAME = "NotifyUser"; 
    private static final String USER_ID_FIELD_NAME = "userId"; 

    private final UserNotifierFactory factory; 
    transient private UserNotifier notifier; 

    public NotifyUserBolt(UserNotifierFactory factory) { 
    checkNotNull(factory); 

    this.factory = factory; 
    } 

    @Override 
    public void prepare(Map stormConf, TopologyContext context) { 
    notifier = factory.createUserNotifier(); 
    } 

    @Override 
    public void execute(Tuple input, BasicOutputCollector collector) { 
    // This check ensures that the time-dependency imposed by Storm has been observed 
    checkState(notifier != null, "Unable to execute because user notifier is unavailable. Was this bolt successfully prepared?"); 

    long userId = input.getLongByField(PreviousBolt.USER_ID_FIELD_NAME); 

    notifier.notifyUser(userId); 

    collector.emit(new Values(userId)); 
    } 

    @Override 
    public void declareOutputFields(OutputFieldsDeclarer declarer) { 
    declarer.declare(new Fields(USER_ID_FIELD_NAME)); 
    } 
} 

prova

public class NotifyUserBoltTest { 

    private NotifyUserBolt bolt; 

    @Mock 
    private TopologyContext topologyContext; 

    @Mock 
    private UserNotifier notifier; 

    // This test implementation allows us to get the mock to the unit-under-test. 
    private class TestFactory implements UserNotifierFactory { 

    private final UserNotifier notifier; 

    private TestFactory(UserNotifier notifier) { 
     this.notifier = notifier; 
    } 

    @Override 
    public UserNotifier createUserNotifier() { 
     return notifier; 
    } 
    } 

    @Before 
    public void before() { 
    MockitoAnnotations.initMocks(this); 

    // The factory will return our mock `notifier` 
    bolt = new NotifyUserBolt(new TestFactory(notifier)); 
    // Now the bolt is holding on to our mock and is under our control! 
    bolt.prepare(new Config(), topologyContext); 
    } 

    @Test 
    public void testExecute() { 
    long userId = 24; 
    Tuple tuple = mock(Tuple.class); 
    when(tuple.getLongByField(PreviousBolt.USER_ID_FIELD_NAME)).thenReturn(userId); 
    BasicOutputCollector collector = mock(BasicOutputCollector.class); 

    bolt.execute(tuple, collector); 

    // Here we just verify a call on `notifier`, but we could have stubbed out behavior befor 
    // the call to execute, too. 
    verify(notifier).notifyUser(userId); 
    verify(collector).emit(new Values(userId)); 
    } 
} 
+0

So che questo è un vecchio post quindi perdonami se sei passato da questo progetto :) Mi chiedo se il test per verificare (notifier) ​​.notifyUser (userId); passato. Sto scoprendo che la serializzazione e la deserializzazione eseguite da Storm sulla fabbrica provocano l'istanziazione di un nuovo notificatore fittizio. Quindi il notificatore fittizio originale non riceve alcuna interazione. Era questo il caso per te? – ilana917

+0

@ ilana917 Storm and mocks non deve interagire. Lo scopo di questo modello è scrivere il codice in un modo che consenta di testare il codice separatamente dal runtime Storm. Nel test, non dovrebbe verificarsi alcuna serializzazione. Tra le nuove 'NotifyUserBolt' e' bolt.prepare', non ci sono serializzazioni nel test. Nel runtime Storm Storm avrebbe serializzato il Bolt. –

+0

Grazie @ carl-g, questa è la direzione che sto seguendo. Sono nel bel mezzo di un grande refactoring e in origine pensavo di gestire storm end-end come una sorta di test di integrazione usando i mock per le connessioni esterne, ma l'unità test della nostra logica ha più senso. – ilana917

11

Un approccio che abbiamo adottato è quello di spostare la maggior parte della logica dell'applicazione su bulloni e beccucci e in oggetti che usiamo per fare il lavoro pesante istanziando e utilizzando tramite interfacce minime. Quindi eseguiamo test unitari su quegli oggetti e test di integrazione, anche se questo lascia un vuoto.

+2

mentre quello che dici è vero, e una buona idea, non arriva all'interesse dell'OP nel trovare cose come i problemi di serializzazione prima di fare una distribuzione di produzione. –

+0

Grazie per la risposta! Sono curioso di sapere cosa intendevi per "lasciare un vuoto"? – taylorcressy

+0

il divario è all'interfaccia tra la tempesta e i nostri oggetti. quella parte non è stata testata a fondo perché compare solo nei test di integrazione, che è costoso da rendere esauriente, quindi c'è un codice di connessione non ben coperto –

1

Si scopre che è abbastanza facile prendere in giro oggetti temporali come OutputDeclarer, Tuple e OutputFieldsDeclarer. Di questi, solo OutputDeclarer vede mai effetti collaterali, quindi codifica la classe mock di OutputDeclarer per essere in grado di rispondere ad ogni tupla e ancoraggio emesso, per esempio. La classe di test può quindi utilizzare le istanze di tali classi di simulazione per configurare facilmente un'istanza di bullone/spout, invocarla e convalidare gli effetti collaterali previsti.

14

Dal strutture di collaudo unità versione 0.8.1 di Storm sono stati esposti tramite Java:

Per un esempio di come utilizzare questa API dare un'occhiata qui:

+6

Effettivamente una buona API, anche se un po 'più di documentazione potrebbe aiutare un molto nel comprenderlo. –

+0

Ecco un post del blog che potrebbe essere utile: http://www.pixelmachine.org/2011/12/17/Testing-Storm-Topologies.html Spiega l'API del clojure per testare la tempesta, ma fondamentalmente è lo stesso come l'API Java. – asmaier

Problemi correlati