Come posso testare un'unità di una classe che richiede una chiamata al servizio web?


21

Sto cercando di testare una classe che chiama alcuni servizi Web di Hadoop. Il codice è praticamente della forma:

method() {
    ...use Jersey client to create WebResource...
    ...make request...
    ...do something with response...
}

ad esempio esiste un metodo di creazione directory, un metodo di creazione cartella ecc.

Dato che il codice ha a che fare con un servizio web esterno su cui non ho il controllo, come posso provare questo? Potrei provare a deridere il client / le risposte del servizio Web, ma ciò rompe le linee guida che ho visto molto recentemente: "Non deridere oggetti che non possiedi". Potrei impostare un'implementazione fittizia del servizio Web - costituirebbe comunque un "test unitario" o sarebbe quindi un test di integrazione? Non è possibile effettuare test unitari a un livello così basso: come farebbe un professionista del TDD?


5
Dove hai visto le indicazioni per non prendere in giro cose che non possiedi? Questa sembra essere una ragione enorme per cui dovresti prendere in giro le cose ...
Thomas Owens


1
@ChrisCooper: posso sottolineare che l'ultimo link è molto obsoleto (dal 2007). Molte cose sono cambiate da allora. Ho la sensazione dal post che il deridere fosse molto più difficile allora quando
dovevi

Risposte:


41

Secondo me dovresti prendere in giro le chiamate al servizio web se questo è un test unitario, al contrario di un test di integrazione.

Il test unitario non deve verificare se il servizio Web esterno funziona o se l'integrazione con esso è corretta. Senza diventare troppo dogmatico riguardo al TDD, nota che un effetto collaterale della trasformazione del test unitario in un test di integrazione è che è probabile che funzioni più lentamente e desideri test unitari rapidi .

Inoltre, se il servizio web è temporaneamente inattivo o non funziona correttamente, ciò dovrebbe causare il fallimento del test dell'unità? Non sembra giusto. Il test dell'unità dovrebbe fallire per un solo motivo: se nel codice è presente un errore in tale "unità".

L'unica parte di codice rilevante qui è ...do something with response.... Deridere il resto.


2
Ricorda che dovrai mantenere la tua finta firma dell'oggetto e restituire i valori sincronizzati con quelli generati dal servizio web di Hadoop durante le inevitabili modifiche a quel servizio.
pcurry

Raramente vale la pena testare componenti standard come Hadoop. Ma se stai chiamando un servizio web personalizzato fornito da un'altra squadra o organizzazione, potresti voler scrivere un test per autodifesa. In questo modo quando le cose vanno male puoi verificare rapidamente se il problema è il tuo codice o il servizio web. Questo non è un test unitario da eseguire automaticamente; è una diagnostica da eseguire secondo necessità.
Kevin Cline,

@kevincline Concordo pienamente sulla necessità dei test che proponi, e in effetti li scrivo nel mio lavoro quotidiano e si sono dimostrati utili. Ma per definizione NON sono test unitari, qual è la domanda :) :) Considera questo: se si tratta di un test unitario e il codice fallisce perché il servizio web è stato modificato, qual è l '"unità" che stai testando? Che cosa è esattamente fallito? Non si esegue il test in isolamento, come richiesto dal test unitario.
Andres F.,

1
@AndresF .: Penso che siamo in un accordo violento: "Questo [diagnostico] NON è un test unitario ..."
Kevin Cline,

@kevincline Right! Ho letto male il tuo commento, scusa!
Andres F.,

5

Non sono d'accordo con "non deridere oggetti che non possiedi" quando stai testando le unità.

Lo scopo beffardo dell'esistenza è il fatto che ci saranno moduli, librerie, classi che non possederemo.

Il mio suggerimento per il tuo scenario è la simulazione della chiamata al servizio web.

Imposta il mock in modo tale da restituire i dati al tuo modulo.
Assicurati di coprire tutto lo scenario, ad esempio quando i dati restituiti sono nulli, quando i dati restituiti sono validi ecc.

E per il codice che possiedi, la tua responsabilità come sviluppatore è quella di garantire che il codice che stai creando funzioni come previsto in tutti gli scenari.


1

Vorrei usare qualcosa come EasyMock per questo test. I framework beffardi sono un modo ideale per rimuovere dipendenze esterne da una classe e ti danno il controllo totale sul risultato delle dipendenze esterne durante i test. Per estendere un po 'il tuo esempio:

class WebClass {

private WebServiceInterface webserviceInterface;

    void method(){
        R result = webServiceInterface.performWebServiceCall();
        ... do something with result
    }

    public void setWebServiceInterface(WebServiceInterface webServiceInterface){
        this.webServiceInterface = webServiceInterface;
    }
}


interface WebServiceInterface {

   R performWebServiceCall();

}


class WebClassTest {

private WebServiceInterface mock;    
private R sampleResult = new R();

    @Before
    public void before(){
        mock = EasyMock.createMock(WebServiceInterface.class);
    }


    @Test
    public void test() {
        WebClass classUnderTest = new WebClass();
        EasyMock.expect(mock.performWebServiceCall()).andReturn(sampleResult);
        classUnderTest.setWebServiceInterface(mock);
        classUnderTest.method();
        EasyMock.verify(mock);
    }
}

La prima cosa che devi fare è estrarre la logica nella tua classe in cui usi Jersey per ottenere una risorsa Web e chiamare il servizio Web in una classe separata. La creazione di un'interfaccia per questa classe ti consentirà di creare una simulazione a cui puoi quindi dettare il comportamento.

Una volta creata questa interfaccia, è possibile creare un mock usando EasyMock, che restituirà un oggetto specificato accedendo al test case. L'esempio sopra è una semplificazione di come strutturare un test simulato di base e come funzionerà la tua interfaccia.

Per ulteriori informazioni sui framework di derisione, vedere questa domanda . Inoltre, questo esempio presuppone l'uso di Java ma i framework di derisione sono disponibili in tutte le lingue e sebbene siano implementati in modo diverso, funzioneranno generalmente allo stesso modo


1

Le beffe sono accettabili in questo caso, ma non ne hai bisogno. Invece di unit test method(), invece unit test solo la parte che gestisce la risposta.

Estrarre una funzione che accetta ResponseData(di qualunque tipo sia appropriato) e quindi esegue l'azione.

Invece di deridere, ora devi solo costruire un oggetto ResponseData e passarlo.

Puoi lasciare la chiamata del servizio a test di integrazione completi, che copriranno method()in totale


0

Quello che ho fatto e funziona:

  1. Avere tutti i servizi web di chiamata in codice tramite proxy.
  2. Il proxy chiama una classe che sa staticamente se stiamo usando il proxy o meno e reindirizza di conseguenza. I mock sono solo HashMaps che per ogni richiesta restituisce una determinata risposta.
  3. Esegui i test più volte in questo ordine:

3.1 Innanzitutto vengono testati tutti i servizi Web. Da ogni macchina, anche macchine per sviluppatori. Questi sono i veri servizi web, ma in esecuzione in ambiente di sviluppo. Ciò significa che i servizi web non possono mai essere inattivi o rispondere a valori errati, perché altrimenti ogni sviluppatore si lamenta di non poterlo compilare.

3.2 Quindi vengono eseguiti tutti i test unitari interni all'applicazione. Ciò significa che tutti i servizi web vengono derisi e testati eseguendo gli stessi test di 3.1 (anbde dovrebbero passare anche loro, altrimenti i mock sono sbagliati) e vengono invocati dalla vera applicazione come se fossero realmente utilizzati. Se i mock sono sbagliati, puoi eseguire il test in 3.1 e registrare quei valori (richiesta, risposta) in una HashMap.

3.3 Quindi vengono eseguiti gli stessi test di 3.2, ma questa volta rispetto ai servizi Web reali in esecuzione nell'ambiente di sviluppo.

Dopo che tutto ciò è stato completato, per il vero ambiente di produzione devi solo fornire l'indirizzo reale per ogni servizio web. Speriamo che questo non richieda troppi cambiamenti nella configurazione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.