Si tratta di un uso appropriato del metodo di reset di Mockito?

54

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

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 voglio controllare someMethod è stato anche invocato da quel particolare test. Qualcosa come il seguente:

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

  // do some things

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

Questo fallisce, perché l'oggetto deriso aveva someMethod invocato due volte. Non voglio che i miei metodi di test si preoccupino degli effetti collaterali del mio metodo getBar() , quindi sarebbe ragionevole reimpostare il mio oggetto fittizio 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 la reimpostazione degli oggetti fittizi è generalmente indicativa di prove sbagliate. Tuttavia, questo mi sembra OK.

Alternativa

La scelta alternativa sembra essere la seguente:

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

che a mio parere obbliga ogni test a conoscere le aspettative di getBar() , per nessun guadagno.

    
posta Duncan Jones 25.02.2013 - 11:37
fonte

3 risposte

47

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

  • atLeastOnce() consente i falsi positivi, il che è un aspetto negativo perché vuoi che i tuoi test siano sempre corretti.
  • times(2) impedisce il falso positivo, ma fa sembrare che ti aspetti due invocazioni piuttosto che dire "so che il costruttore ne aggiunge uno". Inoltre, se qualcosa viene modificato nel costruttore per aggiungere una chiamata in più, il test ha ora la possibilità di un falso positivo. E rimuovere la chiamata causerebbe il fallimento del test perché il test è ora sbagliato invece di ciò che viene testato è sbagliato.

Usando reset() nel metodo helper, si evitano entrambi questi problemi. Tuttavia, devi fare attenzione che resetterà anche qualsiasi stub che hai fatto, quindi fai attenzione. Il motivo principale per cui reset() è scoraggiato è di impedire

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

Questo non è ciò che l'OP sta tentando di fare. Presumo, l'OP 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 la usano tutte hanno contro. Il fatto che l'OP abbia fatto questo post mostra che sta pensando alla situazione e non usa solo ciecamente il metodo di reset.

    
risposta data 27.02.2013 - 15:45
fonte
5

Smart Mockito users hardly use reset feature because they know it could be a sign of poor tests. Normally, you don't need to reset your mocks, just create new mocks for each test method.

Instead of reset() please consider writing simple, small and focused test methods over lengthy, over-specified tests. First potential code smell is reset() in the middle of the test method.

Estratto da i documenti di mockito .

Il mio consiglio è di provare a evitare di usare reset() . A mio avviso, se chiami due volte su un metodo Method, dovresti testarlo (forse si tratta di un accesso al database o di un altro processo lungo 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 (questo è un comportamento errato, ma il test non avrà esito negativo).

    
risposta data 27.02.2013 - 09:57
fonte
2

Assolutamente no. Come spesso accade, la difficoltà che stai riscontrando nello scrivere un test pulito è una grande bandiera rossa sul design del tuo codice di produzione. In questo caso, la soluzione migliore è il 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 costruttore.

new Bar(mockedObject);

diventa:

new Bar(mockedObject.someMethod());

Se ciò comportasse la duplicazione di questa logica in molti punti, considera la possibilità di creare 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, usare reset () è un buon lavoro. Ma siamo chiari: sta indicando che il tuo codice è mal progettato.

    
risposta data 12.10.2017 - 15:52
fonte

Leggi altre domande sui tag