2011-09-18 20 views
8

prerequisiti: sto utilizzando l'ultima versione di Play! framework e la versione di Java (non Scala).Verifica interazioni con servizi esterni

Ho bisogno di pubblicare un messaggio in una coda di messaggi quando viene creato un utente, e mi piacerebbe testarlo. Il mio problema è rendere questo facilmente testabile.

Il controller approccio

in altri ambiti, quello che avrei fatto sarebbe quella di utilizzare l'iniezione del costruttore nel controller e passare in una coda beffato nelle mie prove; tuttavia, con Play! i controller sono statici, il che significa che non posso eseguire new MyController(mockedQueue) nei miei test.

Potrei usare Google Guice e inserire un'annotazione @Inject su un campo statico nel mio controller, ma non mi sembra molto carino, poiché significa che devo rendere pubblico il campo da sostituire nel test , o devo usare un contenitore nei miei test. Preferisco di gran lunga usare l'iniezione del costruttore, ma gioca! non sembra facilitare questo.

L'approccio modello

Si dice spesso la logica dovrebbe essere nel modello, non è il controller. Ciò ha senso; tuttavia, non siamo qui in Ruby e il fatto che le tue entità interagiscano con servizi esterni (e-mail, code di messaggi ecc.) è considerevolmente meno verificabile rispetto a un ambiente dinamico in cui potresti semplicemente sostituire le tue chiamate statiche MessageQueue con un'istanza simulata in volere.

Se faccio in modo che la mia entità effettui la chiamata in coda, come è verificabile?

Ovviamente, entrambe queste situazioni non sono necessarie se eseguo test di integrazione end-to-end, ma preferisco non aver bisogno di una coda di messaggi o di un server SMTP attivato per l'esecuzione dei test.

Quindi la mia domanda è: Come modellino il mio Play! controller e/o modelli per facilitare le interazioni di test con i servizi esterni?

risposta

3

Come vedo, non c'è una soluzione pulita per questo.

È possibile utilizzare uno Abstract Factory per le proprie dipendenze. Questa fabbrica potrebbe avere metodi di setter per gli oggetti che produce.

public class MyController { 
    ... 
    private static ServiceFactory serviceFactory = ServiceFactory.getInstance(); 
    ... 
    public static void action() { 
     ... 
     QueueService queue = serviceFactory.getQueueService(); 
     ... 
    } 

}

Il test sarebbe simile a questa:

public void testAction() { 
    QueueService mock = ... 
    ... 
    ServiceFactory serviceFactory = ServiceFactory.getInstance(); 
    serviceFactory.setQueueService(mock); 
    ... 
    MyController.action(); 
    verify(mock); 
} 

Se non si vuole esporre i metodi setter della fabbrica, è possibile creare un'interfaccia e configurare l'attuazione classe nei tuoi test.

Un'altra opzione potrebbe essere l'uso di PowerMock per i metodi statici di derisione. Ho usato prima e funziona relativamente bene per la maggior parte dei casi. Basta non esagerare, o sei in manutenzione diavolo ...

E infine, dal momento che sei disposto a utilizzare Guice nella tua applicazione, this potrebbe essere un'opzione praticabile.

Buona fortuna!

+0

Un problema * potenziale * con l'approccio 'ServiceFactory' è quando il campo sarà inizializzato; poiché è un campo statico, 'getInstance' può essere chiamato dall'inizializzatore prima che tu abbia la possibilità di sostituire l'istanza. Sto solo pensando ad alta voce, non in realtà ho verificato questo. –

+0

In realtà, nell'esempio ServiceFactory è Singleton. Non si sostituirà ServiceFactory stesso, ma il QueueService lo 'produce'. –

+0

Sì, lo capisco; tuttavia, poiché il campo è statico, esiste la possibilità che venga inizializzato prima di poter sostituire qualsiasi valore restituito da 'getInstance'. In genere non si ha il controllo su quando viene eseguito l'inizializzatore statico, quindi potrebbe succedere prima che si verifichi l'installazione del test. –

2

Sono poco confuso. È possibile chiamare un metodo di un'altra classe

public class Users extends Controller { 
    public static void save(@Valid User user) { 
    //check for user validaton 
    user = user.save(); 
    QueueService queueService = new QueueSerice(); 
    queueService.publishMessage(user); 
    } 
} 

È possibile scrivere casi di test di unità per QueueService utilizzando un finto e scrivere testcase funzionale per gli utenti regolatore Salva metodo.

+0

Forse non ero chiaro. Come sostituiresti 'new QueueService()' con un'istanza di simulazione nel tuo test? Oppure stai dicendo che testare l'unità 'QueueService', ma esegui solo un test funzionale dell'interazione tra 'User' e' QueueService'? –

+0

Se questa è la strada da percorrere, bene; ma nella maggior parte degli altri framework che ho affrontato, è perfettamente fattibile testare l'interazione tra i due senza richiedere che ci sia una vera e propria coda live disponibile. –

2

EDIT: si estende come risposta precedente non era chiaro

La prima idea sarebbe quella di aggiungere il riferimento alla coda al modello, come si dispone di un POJO e l'accesso al costruttore. Come accennato nei commenti qui sotto, l'approccio Modello è problematico quando si pensa all'Hibernate che idrata l'entità, che scarterebbe.

Il secondo approccio sarebbe quello di aggiungere questo riferimento alla coda al controller. Ora, questa sembra una cattiva idea. Oltre alla questione dei membri pubblici che hai citato, credo che l'idea alla base del controller sia quella di recuperare i parametri della richiesta, verificare che siano corretti (checkAuthenticity, validation, etc), inviare la richiesta da elaborare e quindi preparare la risposta.

La "chiave" qui è "invia la richiesta da elaborare".In alcuni casi potremmo farlo funzionare nel Controller se è semplice, ma in altri casi sembra preferibile utilizzare un "Servizio" (per chiamarlo in qualche modo) in cui esegui il lavoro di cui hai bisogno con i dati forniti.

Io uso questa separazione dal punto di vista del test è più facile (per me) testare il controller tramite Selenium e fare un test separato (utilizzando JUnit) per il servizio.

Nel caso in cui questo servizio includesse il riferimento alla coda citata.

Su come inizializzare, ciò dipenderà. È possibile creare un singleton, inizializzarlo ogni volta tramite un costruttore, ecc. In uno scenario particolare ciò può dipendere dal lavoro relativo all'inizializzazione del servizio in coda: se è difficile, è possibile che si desideri un Singleton con un metodo Factory che recuperi il servizio (e può essere deriso nei test) e passarlo come parametro al costruttore dell'oggetto Service.

Spero che questo aggiornamento chiarisca di più ciò che avevo in mente quando ho risposto.

+0

Pollo e l'uovo. Da dove viene la fabbrica? Un singleton? –

+0

Pollo e uova? : | Potresti avere un metodo factory statico, in un singleton o in una utility class o da qualche parte. Il suo unico scopo è restituire l'oggetto Queue di cui hai bisogno. Voglio dire, in quale altro modo vuoi farlo? Ho paura di non vedere il problema o non capisco la tua obiezione. –

+0

In qualsiasi altro framework, utilizzerei l'iniezione del costruttore (con o senza contenitore). L'uso di singleton o metodi factory serve solo a nascondere quali dipendenze una classe abbia veramente. –

1

Forse non è quello che stai cercando, ma nel mio attuale progetto abbiamo risolto quel tipo di test attraverso test di integrazione e una configurazione JMS con una coda locale e un ponte di messaggistica.

In poco più in dettaglio:

  • vostro codice sempre messaggi/legge i messaggi da/code locali, vale a dire le code sul server di applicazione locale (non sul sistema esterno).
  • Un bridge di messaggistica collega la coda locale alla coda del servizio esterno quando necessario, ad es. in produzione o in un ambiente di test manuale.
  • Un test di integrazione crea il nuovo utente (o qualsiasi cosa si desidera testare), quindi legge il messaggio previsto dalla coda locale. In questo caso, il ponte di messaggistica non è attivo.

sul mio progetto, usiamo SoapUI per eseguire questi test come il sistema in prova è una piattaforma di integrazione basato su SOAP e SoapUI ha un buon supporto JMS. Ma potrebbe anche essere un semplice test JUnit che esegue il test e legge dalla coda JMS locale in seguito.