Escribo casos de prueba de jUnit para 3 propósitos:
- Para garantizar que mi código satisfaga toda la funcionalidad requerida, bajo todas (o la mayoría de) las combinaciones / valores de entrada.
- Para asegurarme de que puedo cambiar la implementación y confiar en los casos de prueba JUnit para decirme que toda mi funcionalidad aún está satisfecha.
- Como documentación de todos los casos de uso, mi código maneja y actúa como una especificación para la refactorización, en caso de que alguna vez sea necesario reescribir el código. (Refactorice el código, y si mis pruebas de jUnit fallan, probablemente haya perdido algún caso de uso).
No entiendo por qué o cuándo Mockito.verify()
debería usarse. Cuando veo que me verify()
llaman, me dice que mi jUnit se está dando cuenta de la implementación. (Por lo tanto, cambiar mi implementación rompería mis unidades, aunque mi funcionalidad no se haya visto afectada).
Estoy buscando:
¿Cuáles deberían ser las pautas para el uso apropiado de
Mockito.verify()
?¿Es fundamentalmente correcto que jUnits sea consciente de la implementación de la clase bajo prueba o que esté estrechamente vinculada a ella?
java
unit-testing
junit
mockito
Russell
fuente
fuente
Respuestas:
Si el contrato de la clase A incluye el hecho de que llama al método B de un objeto de tipo C, entonces debe probarlo haciendo una simulación del tipo C y verificando que se haya llamado al método B.
Esto implica que el contrato de la clase A tiene suficientes detalles como para hablar sobre el tipo C (que podría ser una interfaz o una clase). Entonces, sí, estamos hablando de un nivel de especificación que va más allá de los "requisitos del sistema" y describe la implementación.
Esto es normal para las pruebas unitarias. Cuando realiza una prueba de unidad, desea asegurarse de que cada unidad esté haciendo "lo correcto", y eso generalmente incluirá sus interacciones con otras unidades. Las "unidades" aquí pueden significar clases o subconjuntos más grandes de su aplicación.
Actualizar:
Siento que esto no se aplica solo a la verificación, sino también al tropezar. Tan pronto como apriete un método de una clase de colaborador, su prueba de unidad se ha vuelto, en cierto sentido, dependiente de la implementación. Es algo así como la naturaleza de las pruebas unitarias. Dado que Mockito se trata tanto de tropezar como de verificar, el hecho de que esté usando Mockito implica que se encontrará con este tipo de dependencia.
En mi experiencia, si cambio la implementación de una clase, a menudo tengo que cambiar la implementación de sus pruebas unitarias para que coincida. Por lo general, sin embargo, no voy a tener que cambiar el inventario de lo que hay pruebas unitarias son de la clase; a menos, por supuesto, que la razón del cambio fuera la existencia de una condición que no pude probar antes.
Así que de esto se tratan las pruebas unitarias. Una prueba que no sufre este tipo de dependencia en la forma en que se usan las clases de colaboradores es realmente una prueba de subsistema o una prueba de integración. Por supuesto, estos también se escriben frecuentemente con JUnit, y con frecuencia implican el uso de burlas. En mi opinión, "JUnit" es un nombre terrible, para un producto que nos permite producir diferentes tipos de pruebas.
fuente
equals()
oequalsIgnoreCase()
nunca sería algo que se especificara en los requisitos de una clase, por lo que nunca tendría una prueba de unidad per se. Sin embargo, "cerrar la conexión de base de datos cuando se hace" (lo que sea que esto signifique en términos de implementación) puede ser un requisito de una clase, aunque no sea un "requisito comercial". Para mí, esto se reduce a la relación entre el contrato ...La respuesta de David es, por supuesto, correcta, pero no explica por qué querrías esto.
Básicamente, cuando la unidad de prueba está probando una unidad de funcionalidad de forma aislada. Usted prueba si la entrada produce la salida esperada. A veces, también debe probar los efectos secundarios. En pocas palabras, verificar le permite hacer eso.
Por ejemplo, tiene un poco de lógica empresarial que se supone que almacena cosas usando un DAO. Puede hacer esto usando una prueba de integración que crea instancias del DAO, lo conecta a la lógica de negocios y luego hurga en la base de datos para ver si las cosas esperadas se almacenaron. Esa ya no es una prueba unitaria.
O bien, podría burlarse del DAO y verificar que se llame de la manera que espera. Con mockito, puede verificar que se llama a algo, con qué frecuencia se lo llama e incluso usar coincidencias en los parámetros para asegurarse de que se llame de una manera particular.
La otra cara de las pruebas unitarias como esta es que está vinculando las pruebas a la implementación, lo que hace que la refactorización sea un poco más difícil. Por otro lado, un buen olor de diseño es la cantidad de código que se necesita para ejercerlo adecuadamente. Si sus pruebas necesitan ser muy largas, probablemente algo esté mal con el diseño. Por lo tanto, el código con muchos efectos secundarios / interacciones complejas que deben probarse probablemente no sea algo bueno.
fuente
Esta es una gran pregunta! Creo que la causa principal es la siguiente, estamos usando JUnit no solo para pruebas unitarias. Entonces la pregunta debería ser dividida:
así que si ignoramos las pruebas superiores a la unidad, la pregunta puede reformularse: " Usar pruebas unitarias de caja blanca con Mockito.verify () crea una gran pareja entre la prueba unitaria y mi posible implementación, ¿puedo hacer algo de " caja gris? " pruebas unitarias y qué reglas generales debería usar para esto ".
Ahora, repasemos todo esto paso a paso.
* - ¿Debo usar Mockito.verify () en mi integración prueba de (o cualquier otra prueba superior a la unidad)? * Creo que la respuesta es claramente no, además no debes usar simulacros para esto. Su prueba debe estar lo más cerca posible de la aplicación real. Está probando un caso de uso completo, no una parte aislada de la aplicación.
* Recuadro negro vs caja blanca pruebas unitarias * Si está utilizando recuadro negro de enfoque de lo que es realmente haciendo, de alimentación (todas las clases de equivalencia) de entrada, un estado de salida, y las pruebas que va a recibir esperado. En este enfoque, el uso de simulacros en general está justificado (simplemente imitas que están haciendo lo correcto; no quieres probarlos), pero llamar a Mockito.verify () es superfluo.
Si está utilizando un enfoque de caja blanca, ¿qué está haciendo realmente? Está probando el comportamiento. de su unidad. En este enfoque, llamar a Mockito.verify () es esencial, debe verificar que su unidad se comporte como espera.
reglas generales para las pruebas de caja gris El problema con las pruebas de caja blanca es que crea un alto acoplamiento. Una posible solución es hacer pruebas de caja gris, no pruebas de caja blanca. Esta es una especie de combinación de pruebas de caja en blanco y negro. Realmente está probando el comportamiento de su unidad como en las pruebas de caja blanca, pero en general lo hace independiente de la implementación cuando es posible . Cuando sea posible, solo hará una verificación como en el caso de caja negra, solo afirma que la salida es lo que se espera que sea. Entonces, la esencia de su pregunta es cuándo es posible.
Esto es realmente dificil. No tengo un buen ejemplo, pero puedo darte ejemplos. En el caso que se mencionó anteriormente con equals () vs equalsIgnoreCase (), no debe llamar a Mockito.verify (), simplemente afirme la salida. Si no pudo hacerlo, divida su código en la unidad más pequeña, hasta que pueda hacerlo. Por otro lado, suponga que tiene algún @Service y está escribiendo @ Web-Service que es esencialmente un envoltorio en su @Service: delega todas las llamadas al @Service (y realiza un tratamiento adicional de errores). En este caso, llamar a Mockito.verify () es esencial, no debe duplicar todas las comprobaciones que hizo para @Serive, verificando que es suficiente llamar a @Service con la lista de parámetros correcta.
fuente
Debo decir que tiene toda la razón desde el punto de vista de un enfoque clásico:
Es importante recordar que no hay herramientas universales. El tipo de software, su tamaño, los objetivos de la empresa y la situación del mercado, las habilidades del equipo y muchas otras cosas influyen en la decisión sobre qué enfoque utilizar en su caso particular.
fuente
Como algunas personas dijeron
Con respecto a su preocupación por romper sus pruebas al refactorizar, eso es algo esperado cuando se usan simulacros / trozos / espías. Lo digo por definición y no con respecto a una implementación específica como Mockito. Pero podría pensar de esta manera: si necesita hacer una refactorización que crearía cambios importantes en la forma en que funciona su método, es una buena idea hacerlo con un enfoque TDD, lo que significa que puede cambiar su prueba primero para definir el nuevo comportamiento (que fallará la prueba), y luego haga los cambios y vuelva a pasar la prueba.
fuente
En la mayoría de los casos, cuando a la gente no le gusta usar Mockito.verify, es porque se usa para verificar todo lo que está haciendo la unidad probada y eso significa que deberá adaptar su prueba si algo cambia en ella. Pero, no creo que sea un problema. Si desea poder cambiar lo que hace un método sin la necesidad de cambiar su prueba, eso básicamente significa que desea escribir pruebas que no prueben todo lo que está haciendo su método, porque no quiere que pruebe sus cambios . Y esa es la forma incorrecta de pensar.
Lo que realmente es un problema es si puede modificar lo que hace su método y una prueba unitaria que se supone que cubre la funcionalidad por completo no falla. Eso significaría que cualquiera que sea la intención de su cambio, el resultado de su cambio no está cubierto por la prueba.
Por eso, prefiero burlarme tanto como sea posible: también burlarme de sus objetos de datos. Al hacerlo, no solo puede usar la verificación para verificar que se invocan los métodos correctos de otras clases, sino también que los datos que se pasan se recopilan a través de los métodos correctos de esos objetos de datos. Y para completarlo, debe probar el orden en que ocurren las llamadas. Ejemplo: si modifica un objeto de entidad db y luego lo guarda usando un repositorio, no es suficiente verificar que los establecedores del objeto sean llamados con los datos correctos y que se llame al método guardar del repositorio. Si se llaman en el orden incorrecto, su método aún no hace lo que debería hacer. Por lo tanto, no uso Mockito.verify, pero creo un objeto inOrder con todos los simulacros y uso inOrder.verify en su lugar. Y si desea completarlo, también debe llamar a Mockito. verifique NoMoreInteractions al final y páselo todos los simulacros. De lo contrario, alguien puede agregar una nueva funcionalidad / comportamiento sin probarlo, lo que significaría después de que sus estadísticas de cobertura pueden ser del 100% y aún así está acumulando código que no se afirma o verifica.
fuente