¿Es este un uso apropiado del método de reinicio de Mockito?

53

Tengo un método privado en mi clase de prueba que construye un objeto Bar comúnmente usado. El constructor Bar llama al método someMethod() en mi objeto simulado:

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

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

En algunos de mis métodos de prueba, quiero comprobar que someMethod también fue invocado por esa prueba en particular. Algo como lo siguiente:

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

  // do some things

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

Esto falla, porque el objeto simulado tuvo someMethod invocado dos veces. No quiero que mis métodos de prueba se preocupen por los efectos secundarios de mi método getBar() , ¿sería razonable restablecer mi objeto simulado al final de getBar() ?

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

Lo pregunto, porque la documentación sugiere que restablecer objetos simulados es generalmente indicativo de malas pruebas. Sin embargo, esto se siente bien para mí.

Alternativa

La opción alternativa parece estar llamando:

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

que en mi opinión obliga a cada prueba a conocer las expectativas de getBar() , sin ganancia.

    
pregunta Duncan Jones 25.02.2013 - 11:37

3 respuestas

47

Creo que este es uno de los casos en los que usar reset() está bien. La prueba que está escribiendo está probando que "algunas cosas" desencadenan una sola llamada a someMethod() . Escribir la declaración verify() con un número diferente de invocaciones puede generar confusión.

  • atLeastOnce() permite falsos positivos, lo cual es algo malo, ya que quiere que las pruebas sean siempre correctas.
  • times(2) previene el falso positivo, pero parece que está esperando dos invocaciones en lugar de decir "sé que el constructor agrega una". Además, si algo cambia en el constructor para agregar una llamada adicional, la prueba ahora tiene la posibilidad de un falso positivo. Y eliminar la llamada causaría que la prueba fallara porque la prueba ahora es incorrecta en lugar de que lo que se está probando sea incorrecto.

Al utilizar reset() en el método auxiliar, evita estos dos problemas. Sin embargo, debe tener cuidado de que también se reinicie cualquier aplastamiento que haya hecho, así que tenga cuidado. La razón principal por la que no se recomienda reset() es prevenir

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

Esto no es lo que el OP está tratando de hacer. El OP, supongo, tiene una prueba que verifica la invocación en el constructor. Para esta prueba, el restablecimiento permite aislar esta acción única y su efecto. Este uno de los pocos casos con reset() puede ser útil como. Las otras opciones que no lo usan tienen sus contras. El hecho de que el OP haya hecho este post muestra que está pensando en la situación y no solo está utilizando a ciegas el método de reinicio.

    
respondido por el unholysampler 27.02.2013 - 15:45
5
  

Los usuarios de Smart Mockito apenas usan la función de restablecimiento porque lo saben   Podría ser un signo de malas pruebas. Normalmente, no necesitas reiniciar tu   simulacros, simplemente cree nuevos simulacros para cada método de prueba.

     

En lugar de reset() , considere escribir de forma simple, pequeña y centrada   Métodos de prueba sobre pruebas largas y sobre especificadas. Primer codigo potencial   el olor es reset() en el medio del método de prueba.

Extraído de los documentos de mockito .

Mi consejo es que intentes evitar el uso de reset() . En mi opinión, si llama dos veces a algún Método, debería probarlo (tal vez sea un acceso a la base de datos u otro proceso largo del que quiera cuidar).

Si realmente no te importa eso, puedes usar:

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

Tenga en cuenta que esto último podría causar un resultado falso, si llama a algún Método desde getBar, y no después (este es un comportamiento incorrecto, pero la prueba no fallará).

    
respondido por el greuze 27.02.2013 - 09:57
2

Absolutamente no. Como suele ser el caso, la dificultad que tiene para escribir una prueba limpia es una de las principales señales de alerta sobre el diseño de su código de producción. En este caso, la mejor solución es refactorizar su código para que el constructor de Bar no llame a ningún método.

Los constructores deben construir, no ejecutar la lógica. Tome el valor de retorno del método y páselo como un parámetro de constructor.

new Bar(mockedObject);

se convierte en:

new Bar(mockedObject.someMethod());

Si esto resultara en la duplicación de esta lógica en muchos lugares, considere crear un método de fábrica que pueda probarse independientemente de su objeto Bar:

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

Si esta refactorización es demasiado difícil, entonces usar reset () es una buena solución. Pero seamos claros: indica que su código está mal diseñado.

    
respondido por el tonicsoft 12.10.2017 - 15:52

Lea otras preguntas en las etiquetas