Unit test una classe che chiama in sequenza altre classi

3

Ciao ho una classe come questa

class MyClass {

  private ExternalClass1 ex1;
  private ExternalClass2 ex2;
  private ExternalClass3 ex3

public String doSomething(String arg1){ 
 val1=ex1.invoke(arg1); 
 val2=ex2.call(val1); 
 result=ex3.doit(val2);
 return result;
}    

}

test unitario di questo metodo

@Test
void doSometing(){
   ExternalClass1 ex1=mock(ExternalClass1);
   ExternalClass2 ex2=mock(ExternalClass2);
   ExternalClass3 ex3=mock(ExternalClass2);

when(ex1.invoke(arg1)).thenReturn(val1);
when(ex2.call(val1)).thenReturn(val2);
when(ex3.doit(val2)).thenReturn(result);

Myclass myClass=new MyClass(ex1,ex2,ex3);

assertEquals(result,myClass.doSomething(arg1))
}

Il codice di prova di questo metodo sembra essere una semplice ripetizione del codice stesso, ma più complesso. Il test di questo tipo di classe il cui ruolo è controllare altre classi porta qualche valore?

Grazie di

    
posta Belin 30.03.2017 - 18:44
fonte

4 risposte

5

Porta qualche valore?

Sì: sta testando l'interazione.

Se l'unità ha già testato la funzionalità dei tre metodi chiamati, allora è garantito che funzionino correttamente. Questo tipo di test è un Test di stato ; chiama il metodo e verifica di aver ricevuto il risultato corretto.

Ma qui si desidera eseguire un test di interazione ; uno che verifica che alcuni metodi siano stati chiamati correttamente (potresti non sapere cosa questi metodi fanno ai dati, quindi non puoi testare il valore del risultato in modo coerente).

Per eseguire un test di interazione, modifica i tuoi mock per aggiungere un incrementore a ciascun metodo, quindi asserisci che le variabili incrementate sono quelle che ti aspettavi:

Il modo in cui implementate l'incrementor dipenderà dal modo in cui avete creato le vostre classi e da come vi state prendendo gioco.

Come un breve esempio di seudo:

@Test
void doSomething()
{
    ExternalClass1 ex1=mock(ExternalClass1);
    ExternalClass2 ex2=mock(ExternalClass2);
    ExternalClass3 ex3=mock(ExternalClass2);

    int ex1Count = 0;
    int ex2Count = 0;
    int ex3Count = 0;

    when(ex1.invoke(arg1)).do(ex1Count++).thenReturn(val1);
    when(ex2.call(val1)).do(ex2Count++).thenReturn(val2);
    when(ex3.doit(val2)).do(ex3Count++).thenReturn(result);

    Myclass myClass = new MyClass(ex1,ex2,ex3);

    myClass.doSomething(arg1);

    assertEquals(1, ex1Count);
    assertEquals(1, ex2Count);
    assertEquals(1, ex3Count);

    // assertEquals(2, ex1Count); // If for example ex3 utilises ex1 once (and you know that it should)...
}
    
risposta data 30.03.2017 - 22:50
fonte
1

L'unico valore che porta è la copertura del codice, il che significa che hai copertura sul metodo doSometing (). È prezioso? Forse su un rapporto di gestione, ma per quanto riguarda il test / convalida il comportamento è inutile.

Il metodo doSometing () è un wrapper o facciata. Non contiene alcuna logica all'infuori dei richiami ad altri metodi, quindi non ha bisogno di un test. Altri test su ExternalClass1, ExternalClass2 e ExternalClass3 riguarderanno le funzionalità effettive.

    
risposta data 30.03.2017 - 20:42
fonte
1

Potrebbe essere necessario che il test funzionale chiami doSomething e verifichi che sia in un ordine ragionevole da quello. I tuoi test di unità probabilmente controlleranno già ogni parte che invoca.

Una cosa che il test dell'unità può testare per doSomething è la sua gestione degli errori. Se una qualsiasi delle tre chiamate potrebbe non riuscire per determinati input e si hanno determinate aspettative sull'aumento (o meno) delle eccezioni per tali input non validi, può valere la pena di testare. Probabilmente i client di doSomething dipendono da questo e non vogliono dipendere dai dettagli dell'implementazione.

    
risposta data 30.03.2017 - 21:13
fonte
0

No! Non così com'è.

O hai bisogno di molto più profondità, o molto meno -adattando molto alla particolare unità in questione. Così com'è, il tuo test convalida solo che doSomething() restituisce il valore di ritorno di ex3.doit() .

Quindi ...

Se ti interessa come doSomething() fa il suo lavoro ... non è necessariamente atipico per testare un un adattatore , facciata o qualsiasi tipo di wrapper in questo modo. Ma devi bloccare questa implementazione e il tuo test cade tristemente. In realtà non garantisce che il wrapper gestisca correttamente l'intero processo.

Per raggiungere questo livello di affidabilità, devi anche convalidare che ogni metodo interno sia invocato quando previsto, come previsto.

Tuttavia, questo è un bisogno raro, nella mia esperienza. Ho solo veramente dovuto convalidare i dettagli di implementazione interna una volta .. Costruire un logger di richieste API: dovevo assicurarmi che il logger gestisse una connessione HTTP sottostante in un modo molto particolare. E non ero libero di eseguire un test di integrazione completo ... perché avrei sbattuto un'API di produzione (fuori dal mio controllo) con dati di test.

In questo caso, ho effettivamente realizzato falsi completi delle classi HTTP che userei in produzione. Ho registrato ogni piccola interazione tra il mio registratore e la risorsa che stava gestendo. E, ho avuto un sacco di test che hanno affermato i dettagli di quelle interazioni.

Ma, nella maggior parte dei casi, ai tuoi test non dovrebbe importare come funziona doSomething() . Dovrebbero solo preoccuparsi che gli effetti e i risultati siano corretti.

Quindi, se non ti interessa come doSomething() svolge effettivamente il suo lavoro ... scrivi solo un test di integrazione.

Un "test di integrazione" è ancora, a mio parere, un "test unitario". L'unità sembra essere "grande".

    
risposta data 30.03.2017 - 21:55
fonte

Leggi altre domande sui tag