È questo un uso appropriato del metodo di ripristino di Mockito?


68

Ho un metodo privato nella mia classe di test che costruisce un Baroggetto comunemente usato . Il Barcostruttore chiama il someMethod()metodo nel mio oggetto deriso:

private @Mock Foo mockedObject; // My mocked object
...

private Bar getBar() {
  Bar result = new Bar(mockedObject); // this calls mockedObject.someMethod()
}

In alcuni dei miei metodi di test che voglio verificare è someMethodstato anche invocato da quel particolare test. Qualcosa di simile al seguente:

@Test
public void someTest() {
  Bar bar = getBar();

  // do some things

  verify(mockedObject).someMethod(); // <--- will fail
}

Questo non riesce, perché l'oggetto deriso era stato someMethodinvocato due volte. Non voglio che i miei metodi di prova si preoccupino degli effetti collaterali del mio getBar()metodo, quindi sarebbe ragionevole ripristinare il mio oggetto simulato alla fine di getBar()?

private Bar getBar() {
  Bar result = new Bar(mockedObject); // this calls mockedObject.someMethod()
  reset(mockedObject); // <-- is this OK?
}

Chiedo, perché la documentazione suggerisce che il ripristino di oggetti simulati è generalmente indicativo di test errati. Tuttavia, questo mi fa sentire bene.

Alternativa

La scelta alternativa sembra essere quella di chiamare:

verify(mockedObject, times(2)).someMethod();

che secondo me obbliga ogni test a conoscere le aspettative di getBar(), senza alcun guadagno.

Risposte:


60

Credo che questo sia uno dei casi in cui l'utilizzo reset()è ok. Il test che stai scrivendo sta testando che "alcune cose" innesca una singola chiamata someMethod(). Scrivere la verify()dichiarazione con un numero diverso di invocazioni può creare confusione.

  • atLeastOnce() consente falsi positivi, il che è negativo in quanto si desidera che i test siano sempre corretti.
  • times(2)impedisce i falsi positivi, ma fa sembrare che ti aspetti due invocazioni anziché dire "so che il costruttore ne aggiunge uno". Inoltre, se qualcosa cambia nel costruttore per aggiungere una chiamata extra, il test ora ha la possibilità di un falso positivo. E la rimozione della chiamata comporterebbe il fallimento del test perché il test ora è sbagliato invece che ciò che viene testato è sbagliato.

Usando reset()il metodo helper, eviti entrambi questi problemi. Tuttavia, devi stare attento che ripristinerà anche qualsiasi mobbing che hai fatto, quindi fai attenzione. Il motivo principale per cui reset()è scoraggiato è prevenire

bar = mock(Bar.class);
//do stuff
verify(bar).someMethod();
reset(bar);
//do other stuff
verify(bar).someMethod2();

Questo non è ciò che l'OP sta cercando di fare. L'OP, presumo, ha un test che verifica l'invocazione nel costruttore. Per questo test, il reset consente di isolare questa singola azione e il suo effetto. Questo uno dei pochi casi con reset()può essere utile come. Le altre opzioni che non lo usano hanno tutti dei contro. Il fatto che l'OP abbia pubblicato questo post dimostra che sta pensando alla situazione e non sta semplicemente utilizzando ciecamente il metodo di ripristino.


17
Vorrei che Mockito avesse fornito la chiamata resetInteractions () per dimenticare solo le interazioni passate allo scopo di verificare (..., tempi (...)) e mantenere il mobbing. Ciò renderebbe le situazioni di test di {setup; atto; verifica;} molto più facile da gestire. Sarebbe {setup; resetInteractions; atto; verifica}
Arkadiy,

2
In realtà dal Mockito 2.1 fornisce un modo per cancellare le invocazioni senza ripristinare gli stub:Mockito.clearInvocations(T... mocks)
Colin D Bennett

6

Gli utenti di Smart Mockito usano a malapena la funzione di ripristino perché sanno che potrebbe essere un segno di scarsi test. Normalmente, non è necessario reimpostare le simulazioni, basta creare nuove simulazioni per ciascun metodo di prova.

Invece di reset()prendere in considerazione la possibilità di scrivere metodi di prova semplici, piccoli e mirati su test lunghi e sopra specificati. Il primo potenziale odore di codice è reset()nel mezzo del metodo di prova.

Estratto dai documenti mockito .

Il mio consiglio è che si tenta di evitare l'uso reset(). Secondo me, se chiami due volte su SomeMethod, questo dovrebbe essere testato (forse è un accesso al database o un altro lungo processo di cui vuoi occuparti).

Se davvero non ti interessa, puoi usare:

verify(mockedObject, atLeastOnce()).someMethod();

Si noti che quest'ultimo potrebbe causare un risultato falso, se si chiama someMethod da getBar e non dopo (si tratta di un comportamento errato, ma il test non fallirà).


2
Sì, ho visto la citazione esatta (l'ho collegata alla mia domanda). Attualmente devo ancora vedere una discussione decente sul perché il mio esempio sopra sia "cattivo". Puoi fornire uno?
Duncan Jones,

Se devi reimpostare i tuoi oggetti finti, sembra che stai provando a testare troppe cose nel tuo test. Puoi dividerlo in due test, testando cose più piccole. In ogni caso, non so perché stai verificando all'interno del metodo getBar, è difficile tenere traccia di ciò che stai testando. Ti consiglio di progettare il tuo test pensando a cosa dovrebbe fare la tua classe (se devi chiamare someMethod esattamente due volte, almeno una volta, solo una volta, mai, ecc.), E fare tutte le verifiche nello stesso posto.
greuze,

Ho modificato la mia domanda per evidenziare che il problema persiste anche se non chiamo il verifymio metodo privato (che sono d'accordo, probabilmente non appartiene a questo). Accolgo con favore i vostri commenti sull'eventuale modifica della risposta.
Duncan Jones,

Ci sono molte buone ragioni per usare reset, in questo caso non presterei troppa attenzione a quella citazione di mockito. Puoi avere il JUnit Class Runner di Spring quando esegui una suite di test causando interazioni indesiderate, specialmente se stai facendo dei test che coinvolgono chiamate di database derise o chiamate che coinvolgono metodi privati ​​su cui non ti interessa usare la riflessione.
Sandy Simonton,

Di solito lo trovo difficile quando voglio testare più cose, ma JUnit semplicemente non offre alcun modo carino (!) Di parametrizzare i test. A differenza di NUnit, ad esempio con le annotazioni.
Stefan Hendriks,

3

Assolutamente no. Come spesso accade, la difficoltà che stai incontrando nello scrivere un test pulito è una grande bandiera rossa sulla progettazione del tuo codice di produzione. In questo caso, la soluzione migliore è refactoring del codice in modo che il costruttore di Bar non chiami alcun metodo.

I costruttori dovrebbero costruire, non eseguire la logica. Prendi il valore di ritorno del metodo e passalo come parametro del costruttore.

new Bar(mockedObject);

diventa:

new Bar(mockedObject.someMethod());

Se ciò comporterebbe la duplicazione di questa logica in molti punti, prendere in considerazione la creazione di un metodo factory che può essere testato indipendentemente dall'oggetto Bar:

public Bar createBar(MockedObject mockedObject) {
    Object dependency = mockedObject.someMethod();
    // ...more logic that used to be in Bar constructor
    return new Bar(dependency);
}

Se questo refactoring è troppo difficile, utilizzare reset () è una buona soluzione. Ma siamo chiari: significa che il tuo codice è mal progettato.

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.