2013-04-12 18 views
34

Ho chiesto recentemente un paio di domande orientate a jUnit e Mockito e sto ancora lottando davvero per farcela. Le esercitazioni sono tutte per esempi molto semplici, quindi sto facendo fatica ad aumentare i miei casi di test per lavorare per le mie lezioni.Utilizzo di Mockito per lo stub e l'esecuzione dei metodi per il test

Attualmente sto cercando di scrivere alcuni casi di test per un metodo che ho in uno dei miei agenti in una webapp. Il metodo interagisce con un paio di altri metodi all'interno dell'agente per convalidare alcuni oggetti. Voglio solo testare questo metodo in questo momento.

Ecco quello che ho cercato di fare:

  1. Creare un oggetto Mockito del mio agente in questo modo:

    MyProcessingAgent mockMyAgent = Mockito.mock(MyProcessingAgent.class);

  2. stub Setup (si spera il termine giusto) utilizzando Mockito. quando così:

    Mockito.when(mockMyAgent.otherMethod(Mockito.any(arg1)).thenReturn(requiredReturnArg);

  3. Prova eseguire il mio metodo in questo modo:

    List myReturnValue = mockMyAgent.methodThatNeedsTestCase();

mi aspettavo di cose in myReturnValue, ma ha ricevuto 0 invece così ho cercato di eseguire il debug. Quando chiamo il metodo, non si esegue mai. Ho un punto di debug sulla prima riga del metodo che non viene mai toccato.

Se voglio eseguire il codice in un metodo di una classe, ma impone altri metodi nella classe (uno che tenta di interagire con i database nel mondo esterno) per restituire valori simulati. È possibile con Mockito?

Sembra che il mio attuale metodo di approccio non sia uno stile di prova corretto, ma non sono sicuro di come andare avanti. Posso prendere in giro la mia classe e avere un metodo eseguito come normale mentre altri metodi vengono stubati per restituire i miei valori dati in modo che non debba gestire l'accesso ai dati durante il test di questo metodo?

risposta

48

Stai confondendo un Mock con un Spy.

In un simulato tutti i metodi vengono stub e restituiscono "tipi di ritorno intelligente". Ciò significa che la chiamata a qualsiasi metodo su una classe mocked non comporterà nulla a meno che non si specifichi il comportamento.

In una spia, la funzionalità originale della classe è ancora presente ma è possibile convalidare le chiamate di metodi in una spia e anche sovrascrivere il comportamento del metodo.

Quello che vuoi è

MyProcessingAgent mockMyAgent = Mockito.spy(MyProcessingAgent.class); 

Un esempio veloce:

static class TestClass { 

    public String getThing() { 
     return "Thing"; 
    } 

    public String getOtherThing() { 
     return getThing(); 
    } 
} 

public static void main(String[] args) { 
    final TestClass testClass = Mockito.spy(new TestClass()); 
    Mockito.when(testClass.getThing()).thenReturn("Some Other thing"); 
    System.out.println(testClass.getOtherThing()); 
} 

uscita è:

Some Other thing 

NB: Si dovrebbe cercare di deridere le dipendenze per l'essere di classe testato non la classe stessa.

+0

Per favore, vedi la mia dichiarazione riguardo alle spie e fammi sapere se non sei d'accordo. –

+0

@JohnB Penso che tu abbia ragione. Ho ampliato la risposta. Non mi rendevo conto che l'OP stava chiamando il metodo internamente. –

+0

Grazie per le descrizioni dettagliate, è molto utile. Quindi, quello che entrambe le risposte stanno raccomandando (per chiarire) è che sto attaccando la classe sbagliata con Mock. Dovrei invece prendere in giro tutti gli oggetti di accesso ai dati di cui la mia classe ha bisogno, quindi usare una normale istanza della mia classe per testare? – Kyle

5

Quindi, l'idea di prendere in giro la classe sottoposta a test è anatema alla pratica del test. NON dovresti farlo. Perché hai fatto così, il tuo test sta entrando nelle classi beffarde di Mockito e non nella tua classe sotto esame.

Spiare non funzionerà anche perché questo fornisce solo un wrapper/proxy attorno alla classe spiata. Una volta che l'esecuzione è all'interno della classe, non passerà attraverso il proxy e quindi non colpirà la spia. AGGIORNAMENTO: anche se credo che questo sia vero per i proxy di Spring, sembra non essere vero per le spie di Mockito. Ho impostato uno scenario in cui il metodo m1() chiama m2(). Spio l'oggetto e lo stub m2() a doNothing. Quando invoco m1() nel mio test, non viene raggiunto il valore m2() della classe. Mockito invoca il mozzicone. Quindi usare una spia per realizzare ciò che viene chiesto è possibile. Tuttavia, vorrei ribadire che considererei una cattiva pratica (IMHO).

È necessario prendere in giro tutte le classi da cui dipende la classe in prova. Questo ti permetterà di controllare il comportamento dei metodi invocati dal metodo sotto test in quanto controlli la classe che questi metodi invocano.

Se la classe crea istanze di altre classi, prendere in considerazione l'utilizzo di fabbriche.

+0

+1 per l'uso di una parola come anatema, sembra duro, ma penso di capire il tuo punto. Sto ancora cercando di avvolgere la mia testa nei test, credo di essermi appena imbattuto nell'angolo completamente sbagliato. – Kyle

+1

probabilmente vero di una parola A. Il fatto che tu stia facendo anche un test unitario è un buon segno. Continuate così. Saluti! –

+0

'+ 1' buona risposta – andyb

3

Hai quasi capito. Il problema è che il Class Under Test (CUT) non è stato creato per il test dell'unità: non è stato "d.

Pensa a questo e hellip;

  • ho bisogno di testare una funzione di una classe - chiamiamola myFunction
  • Tale funzione effettua una chiamata a una funzione in un'altra classe/servizio/database di
  • che funzionano anche chiama un altro metodo su la CUT

Nel test dell'unità

  • dovrebbe creare una concreta CUT o @Spy su di esso
  • È possibile @Mock tutta la classe altro/servizio/database (vale a dire dipendenze esterne)
  • È potrebbe stub l'altra funzione chiamata nel CUT ma non è davvero come unità test dovrebbe essere fatto

Al fine di evitare l'esecuzione di codice che non si è strettamente test, è possibile astrarre quel codice in qualcosa che può essere @Mock ed.

In questo molto semplice esempio, una funzione che crea un oggetto sarà difficile per testare

public void doSomethingCool(String foo) { 
    MyObject obj = new MyObject(foo); 

    // can't do much with obj in a unit test unless it is returned 
} 

Ma una funzione che utilizza un servizio per ottenere MyObject è facile testare, come abbiamo sottratto il difficile/impossibile testare il codice in qualcosa che rende questo metodo verificabile.

public void doSomethingCool(String foo) { 
    MyObject obj = MyObjectService.getMeAnObject(foo); 
} 

come MyObjectService possono essere schernito e inoltre verificato che .getMeAnObject() viene chiamato con il variabile foo.

+0

se _myFunction_ e _myFunction2_ fanno parte del ** CUT ** e _myFunction_ chiama _myFunction2_ come indicato nell'esempio, il mocking parziale basato su Spy non sarebbe l'unica opzione? dovrebbe _myFunction2_ essere spostato fuori da ** CUT ** e "iniettato" anche se è parte integrante della responsabilità ** CUT **? cosa mi manca? – Nishith

+0

Non penso che ti manchi qualcosa. La simulazione parziale basata sulla spia è l'opzione migliore, sebbene tu possa semplicemente testare entrambi i metodi insieme. Il mio esempio riguardava maggiormente il problema quando i metodi in * CUT * creano oggetti su cui si potrebbe voler verificare il comportamento o asserire valori, e ciò potrebbe essere ottenuto rifasando il * CUT * per usare un factory o un servizio esterno per crearli oggetti che possono essere derisi e iniettati. – andyb

+0

grazie ... ha senso ... – Nishith

0

BREVE RISPOSTA

Come fare nel tuo caso:

int argument = 5; // example with int but could be another type 
Mockito.when(mockMyAgent.otherMethod(Mockito.anyInt()).thenReturn(requiredReturnArg(argument)); 

risposta lunga

In realtà ciò che si vuole fare è possibile, almeno in Java 8. Forse non hai ottenere questa risposta da altre persone perché sto utilizzando Java 8 che consente che e questa domanda sia prima del rilascio di Java 8 (che consente di passare funzioni, non solo valori ad altre funzioni).

Simuliamo una chiamata a una query di DataBase. Questa query restituisce tutte le righe di HotelTable che hanno FreeRoms = X e StarNumber = Y. Quello che mi aspetto durante il test, è che questa query restituirà un elenco di hotel diversi: ogni hotel restituito ha lo stesso valore X e Y, mentre gli altri valori e li deciderò in base alle mie esigenze. Il seguente esempio è semplice ma ovviamente puoi renderlo più complesso.

Così ho creare una funzione che restituirà risultati diversi, ma tutti hanno FreeRoms = X e Y. StarNumber =

static List<Hotel> simulateQueryOnHotels(int availableRoomNumber, int starNumber) { 
    ArrayList<Hotel> HotelArrayList = new ArrayList<>(); 
    HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Rome, 1, 1)); 
    HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Krakow, 7, 15)); 
    HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Madrid, 1, 1)); 
    HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Athens, 4, 1)); 

    return HotelArrayList; 
} 

Forse Spy è meglio (riprova), ma ho fatto questo su un classe derisa. Ecco come faccio (notare i valori anyInt()):

//somewhere at the beginning of your file with tests... 
@Mock 
private DatabaseManager mockedDatabaseManager; 

//in the same file, somewhere in a test... 
int availableRoomNumber = 3; 
int starNumber = 4; 
// in this way, the mocked queryOnHotels will return a different result according to the passed parameters 
when(mockedDatabaseManager.queryOnHotels(anyInt(), anyInt())).thenReturn(simulateQueryOnHotels(availableRoomNumber, starNumber)); 
Problemi correlati