java - uso - Cuándo usar Mockito.verify()?
tutorial mockito java (5)
Escribo casos de prueba jUnit para 3 propósitos:
- Para garantizar que mi código cumpla con todas las funciones requeridas, debajo de todas (o la mayoría de) las combinaciones / valores de entrada.
- Para garantizar que puedo cambiar la implementación, y confío en los casos de prueba de JUnit para decirme que toda mi funcionalidad aún está satisfecha.
- Como documentación de todos los casos de uso que mi código maneja, y actúa como una especificación para la refactorización, en caso de que el código tenga que ser reescrito alguna vez. (Refactorice el código, y si mis pruebas jUnit fallan, probablemente se haya salteado algún caso de uso).
No entiendo por qué o cuándo debe usarse Mockito.verify()
. Cuando veo que se llama a verify verify()
, me dice que mi jUnit está tomando conciencia de la implementación. (Por lo tanto, cambiar mi implementación rompería mis jUnits, aunque mi funcionalidad no se vio afectada).
Estoy buscando:
¿Cuáles deberían ser las pautas para el uso apropiado de
Mockito.verify()
?¿Es fundamentalmente correcto que jUnits tenga conocimiento o esté estrechamente vinculado a la implementación de la clase bajo prueba?
Como algunas personas dijeron
- A veces no tienes un resultado directo en el que puedas afirmar
- A veces solo necesita confirmar que su método probado está enviando los resultados indirectos correctos a sus colaboradores (que se burla).
En cuanto a su preocupación por romper sus pruebas al refactorizar, eso es algo que se espera al usar burlas / talones / espías. Me refiero a eso por definición y no a una implementación específica como Mockito. Pero podría pensar de esta manera: si necesita hacer una refactorización que genere cambios importantes en la forma en que funciona su método, es una buena idea hacerlo con un enfoque TDD, lo que significa que primero puede cambiar la prueba para definir el comportamiento nuevo (que no pasará la prueba) y luego realice los cambios para que la prueba se pase nuevamente.
Debo decir que tiene toda la razón desde el punto de vista de un enfoque clásico:
- Si primero crea (o cambia) la lógica comercial de su aplicación y luego la cubre con (adopte) pruebas ( método de Prueba-Última ), entonces será muy doloroso y peligroso dejar que las pruebas sepan cómo funciona su software, aparte de revisando entradas y salidas.
- Si está practicando un enfoque basado en la prueba , sus pruebas serán las primeras en escribirse, cambiarse y reflejar los casos de uso de la funcionalidad de su software. La implementación depende de las pruebas. Eso a veces significa que desea que su software se implemente de alguna manera particular, por ejemplo, confíe en el método de algún otro componente o incluso llámelo una cantidad determinada de veces. ¡ Mockito.verify() es donde Mockito.verify() es útil!
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 usar en su caso particular.
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:
- ¿Debo usar Mockito.verify () en mi prueba de integración (o cualquier otra prueba superior a la unidad)?
- ¿Debería usar Mockito.verify () en mi unidad de pruebas de black-box ?
- ¿Debo usar Mockito.verify () en mi prueba de unidad de caja blanca ?
así que si ignoraremos las pruebas más allá de la unidad, la pregunta se puede reformular " Al usar la prueba de unidad de caja blanca con Mockito.verify () se crean excelentes relaciones entre la prueba unitaria y la implementación de mi caso, ¿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 prueba de integración (o cualquier otra prueba más allá de la unidad)? * Creo que la respuesta es claramente no, además no deberías usar burlas para esto. Su prueba debe ser lo más cercana posible a la aplicación real. Está probando un caso de uso completo, no una parte aislada de la aplicación.
* black-box vs white-box unit-testing * Si está utilizando el enfoque de caja negra, ¿qué es lo que realmente está haciendo? Usted proporciona (todas las clases de equivalencia) entradas, un estado , y prueba que recibirá el resultado esperado. En este enfoque, el uso de burlas en general se justifica (simplemente imita que están haciendo lo correcto, no quieres probarlos), pero llamar a Mockito.verify () es superfluo.
Si está utilizando el enfoque de caja blanca, ¿qué está realmente haciendo? 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 de uso para 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. Este es un tipo 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, simplemente realizará una comprobación como en el caso de la caja negra, solo afirma que la salida es lo que se espera que sea. Entonces, la esencia de tu pregunta es cuando es posible.
Esto es realmente difícil. 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 puede hacerlo, descomponga su código en la unidad más pequeña, hasta que pueda hacerlo. Por otro lado, supongamos que tiene algún @Service y está escribiendo @ Web-Service, que es esencialmente envoltorio de su @Service - delega todas las llamadas al @Service (y realiza un manejo extra de errores). En este caso, llamar a Mockito.verify () es esencial, no debe duplicar todas las comprobaciones que hizo para @Serive, verificando que está llamando a @Service con la lista de parámetros correcta suficiente.
La respuesta de David es correcta, por supuesto, pero no explica por qué querrías esto.
Básicamente, cuando se realizan pruebas unitarias, se prueba una unidad de funcionalidad de forma aislada. Usted prueba si la entrada produce el resultado esperado. A veces, también debes probar los efectos secundarios. En pocas palabras, verificar te permite hacer eso.
Por ejemplo, tiene un poco de lógica comercial que se supone que almacena cosas usando un DAO. Puede hacer esto usando una prueba de integración que instancia el DAO, lo engancha a la lógica comercial y luego hurga en la base de datos para ver si se almacenaron las cosas esperadas. Ya no es una prueba unitaria.
O bien, podría burlarse del DAO y verificar que se llame de la manera esperada. Con mockito puedes verificar que se llama algo, con qué frecuencia se llama, e incluso utilizar los matchers en los parámetros para garantizar que se llame de una manera particular.
La otra cara de la prueba unitaria como esta es que estás 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 ejercitarlo adecuadamente. Si sus pruebas deben ser muy largas, probablemente algo esté mal con el diseño. Entonces, el código con muchos efectos secundarios / interacciones complejas que deben ser probadas probablemente no sea bueno.
Si el contrato de la clase A incluye el hecho de que llama al método B de un objeto de tipo C, debe probarlo haciendo un simulacro de tipo C y verificando que se ha llamado al método B.
Esto implica que el contrato de clase A tiene suficientes detalles que habla de 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 de alguna manera describe la implementación.
Esto es normal para las pruebas unitarias. Cuando está realizando pruebas unitarias, quiere asegurarse de que cada unidad esté haciendo lo "correcto", y eso generalmente incluirá sus interacciones con otras unidades. "Unidades" aquí podría significar clases, o subconjuntos más grandes de su aplicación.
Actualizar:
Siento que esto no se aplica solo a la verificación, sino al tropezar también. Tan pronto como finalice un método de una clase colaboradora, su prueba unitaria se ha convertido, en cierto sentido, en dependiente de la implementación. Es algo así como la naturaleza de las pruebas unitarias serlo. Dado que Mockito se trata tanto de trozos como de verificación, 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. Normalmente, sin embargo, no tendré que cambiar el inventario de las pruebas unitarias que hay para la clase; a menos que, por supuesto, 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 de este tipo de dependencia en la forma en que se utilizan las clases colaboradoras es realmente una prueba de subsistema o una prueba de integración. Por supuesto, con frecuencia se escriben 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 todos los diferentes tipos de prueba.