unit testing - unitarias - ¿Cómo dice que las pruebas de su unidad son correctas?
tdd (16)
Solo hice pruebas unitarias menores en varios momentos de mi carrera. Cada vez que empiezo a bucear nuevamente, siempre me preocupa cómo demostrar que mis pruebas son correctas. ¿Cómo puedo saber que no hay un error en mi prueba de unidad? Por lo general, termino ejecutando la aplicación, comprobando que funciona, y luego usando la prueba unitaria como una especie de prueba de regresión. ¿Cuál es el enfoque recomendado y / o cuál es el enfoque que adoptas para este problema?
Editar: También me doy cuenta de que podría escribir pequeñas pruebas unitarias granulares que serían fáciles de entender. Sin embargo, si usted supone que el código pequeño y granular es impecable ya prueba de balas, puede escribir programas pequeños y granulares y no necesitar pruebas unitarias.
Edit2: Para los argumentos "la prueba unitaria es para asegurarse de que los cambios no rompan nada" y "esto solo ocurrirá si la prueba tiene exactamente el mismo defecto que el código", ¿y si la prueba se cubre? Es posible pasar el código bueno y el malo con una mala prueba. Mi pregunta principal es ¿qué tan buenas son las pruebas unitarias ya que si sus pruebas pueden ser defectuosas realmente no puede mejorar su confianza en su código, realmente no puede probar que su refactorización funcionó, y realmente no puede probar que cumplió con las especificaciones?
Dominic mencionó que "Para que esto sea un problema, tu código debería tener errores de una manera que casualmente hace que tus pruebas pasen". Una técnica que puedes usar para ver si esto es un problema es la prueba de mutación . Hace cambios a su código y ve si hace que las pruebas de la unidad fallen. Si no lo hace, puede indicar áreas donde las pruebas no son 100% exhaustivas.
- La complejidad del código de prueba de unidad es (o debería ser) menor (a menudo órdenes de magnitud menor) que el código real
- La posibilidad de que codifique un error en su prueba de unidad que coincida exactamente con un error en su código real es mucho menos que solo codificar el error en su código real (si codifica un error en su prueba de unidad que no coincide con un error en tu código real debería fallar). Por supuesto, si has hecho suposiciones incorrectas en tu código real, es probable que vuelvas a hacer la misma suposición, aunque el conjunto mental de pruebas unitarias aún debería reducir incluso ese caso.
- Como ya se mencionó, cuando escribes una prueba unitaria tienes (o deberías) tener una mentalidad diferente. Al escribir código real estás pensando "cómo resuelvo este problema". Al escribir una prueba unitaria, estás pensando: "¿cómo pruebo cada posible forma de que esto se rompa?"
Como otros ya han dicho, no se trata de si puede probar que las pruebas unitarias son correctas y completas (aunque es casi seguro que sea mucho más fácil con el código de prueba), ya que reduce el número de errores a un número muy bajo y lo reduce y más bajo.
Por supuesto, tiene que llegar un punto en el que su confianza en su unidad pruebe lo suficiente como para confiar en ellos, por ejemplo, al hacer refactorizaciones. Llegar a este punto suele ser solo un caso de experiencia e intuición (aunque existen herramientas de cobertura de código que ayudan).
Bueno, Dijkstra dijo:
"Las pruebas muestran la presencia, no la ausencia de errores"
IOW, ¿cómo escribirías una prueba unitaria para la función add (int, int)?
IOW, es difícil.
Como se indicó anteriormente, la mejor manera es escribir la prueba antes del código real. Encuentre ejemplos de la vida real del código de su prueba también, si corresponde (fórmula matemática o similar), y compare la prueba de la unidad y el resultado esperado con eso.
Esto es algo que molesta a todos los que usan pruebas unitarias. Si tuviera que darle una respuesta breve, le diría que siempre confíe en sus pruebas unitarias. Pero yo diría que esto debería respaldarse con tu experiencia previa:
- ¿Tuvo algún defecto que se informara de las pruebas manuales y la prueba de la unidad no se detectó (aunque fue responsable) porque hubo un error en su prueba?
- ¿Tuviste falsos negativos en el pasado?
- ¿Son las pruebas de tu unidad lo suficientemente simples?
- ¿Los escribes antes del nuevo código o al menos en paralelo?
Hay dos formas de ayudar a garantizar la exactitud de las pruebas de su unidad:
- TDD: primero escribe la prueba y luego escribe el código que debe probar. Eso significa que puedes verlos fallar. Si sabe que detecta al menos algunas clases de errores (como "No he implementado ninguna funcionalidad en la función que deseo probar todavía"), entonces sabe que no es completamente inútil. Todavía puede dejar pasar otros errores, pero sabemos que la prueba no es completamente incorrecta.
- Tener muchas pruebas Si una prueba permite que algunos errores pasen desapercibidos, lo más probable es que causen errores más adelante en la línea, lo que ocasionará que otras pruebas fallen. Cuando lo note y arregle el código ofensivo, tendrá la oportunidad de examinar por qué la primera prueba no detectó el error como se esperaba.
Y, por último, por supuesto, mantenga las pruebas unitarias tan simples que es poco probable que contengan errores.
La prueba unitaria debe expresar el "contrato" de lo que sea que esté probando. Es más o menos la especificación de la unidad puesta en el código. Como tal, dadas las especificaciones, debería ser más o menos obvio si las pruebas de la unidad son "correctas".
Pero no me preocuparía demasiado acerca de la "corrección" de las pruebas unitarias. Son parte del software y, como tales, también podrían ser incorrectos. El punto de las pruebas unitarias, de mi punto de vista, es que aseguran que el "contrato" de su software no se rompa por accidente . Eso es lo que hace que las pruebas unitarias sean tan valiosas: puedes cavar en el software, refactorizar algunas partes, cambiar los algoritmos en otros, y las pruebas de tu unidad te dirán si has roto algo. Incluso las pruebas unitarias incorrectas te dirán eso.
Si hay un error en las pruebas de su unidad, lo sabrá, porque la prueba de la unidad falla mientras el código probado resulta ser correcto. Bueno, entonces, arregla la prueba unitaria. No es gran cosa.
No lo dices. En general, las pruebas serán más simples que el código que están probando, por lo que la idea es simplemente que tendrán menos probabilidades de tener errores que el código real.
No puede probar que las pruebas son correctas, y si lo está haciendo, lo está haciendo mal.
Las pruebas unitarias son una primera pantalla, una prueba de humo, como todas las pruebas automáticas. Están principalmente allí para decirte si un cambio que hagas más tarde se rompe. No están diseñados para ser una prueba de calidad, incluso con una cobertura del 100%.
Sin embargo, la métrica hace que la administración se sienta mejor, ¡y eso a veces es útil en sí misma!
Para que esto sea un problema, su código debería tener errores de una manera que casualmente hace que sus pruebas pasen. Esto me sucedió recientemente, donde estaba comprobando que una condición dada (a) hacía que fallara un método. La prueba pasó (es decir, el método falló), pero pasó porque otra condición (b) causó una falla. Escriba sus pruebas cuidadosamente y asegúrese de que las pruebas de la unidad prueben UNA cosa.
En general, sin embargo, las pruebas no se pueden escribir para demostrar que el código está libre de errores. Son un paso en la dirección correcta.
Primero, permítanme comenzar diciendo que las pruebas unitarias NO son solo sobre pruebas. Se trata más sobre el diseño de la aplicación. Para ver esto en acción, debe colocar una cámara con su pantalla y registrar su codificación mientras escribe las pruebas de la unidad. Se dará cuenta de que está tomando muchas decisiones de diseño al escribir pruebas unitarias.
¿Cómo saber si las pruebas de mi unidad son buenas?
¡No puedes probar el período de parte lógica! Si su código dice que 2 + 2 = 5 y su prueba se está asegurando de que 2 + 2 = 5, entonces para usted 2 + 2 es 5. Para escribir buenas pruebas de unidad, DEBE tener una buena comprensión del dominio con el que está trabajando. Cuando sepa lo que está tratando de lograr, redactará buenas pruebas y un buen código para lograrlo. Si tiene muchas pruebas de unidad y sus suposiciones son incorrectas, tarde o temprano descubrirá sus errores.
Supongo que escribir la prueba primero (antes de escribir el código) es una buena manera de asegurarse de que la prueba sea válida.
O podrías escribir pruebas para tus pruebas unitarias ...: P
Esta es una de las ventajas de TDD: el código actúa como una prueba para las pruebas.
Es posible que cometas errores equivalentes, pero es poco común en mi experiencia.
Pero ciertamente tuve el caso donde escribo una prueba que debería fallar solo para que pase, lo que me dijo que mi prueba estaba equivocada.
Cuando estaba por primera vez aprendiendo pruebas unitarias, y antes de hacer TDD, también rompía deliberadamente el código después de escribir la prueba para asegurarme de que fallara como esperaba. Cuando no sabía, la prueba estaba rota.
Realmente me gusta la descripción de Bob Martin de esto como equivalente a la contabilidad de doble entrada.
Tenía la misma pregunta, y después de leer los comentarios, esto es lo que ahora pienso (con el debido crédito a las respuestas anteriores):
Creo que el problema puede ser que ambos tomamos el propósito aparente de pruebas unitarias, para probar que el código es correcto, y aplicamos ese propósito a las pruebas mismas. Eso está bien hasta donde llegue, excepto que el propósito de las pruebas unitarias no es probar que el código sea correcto .
Como con todos los esfuerzos no triviales, nunca puedes estar 100% seguro. El propósito correcto de las pruebas unitarias es reducir errores , no eliminarlos. Más específicamente, como otros han notado, cuando haces cambios más tarde eso podría romper algo accidentalmente. Las pruebas unitarias son solo una herramienta para reducir errores, y ciertamente no deberían ser las únicas. Idealmente, combina las pruebas unitarias con la revisión del código y el control de calidad sólido para reducir los errores a un nivel tolerable.
Las pruebas unitarias son mucho más simples que tu código; no es posible hacer que su código sea tan simple como una prueba de unidad si su código hace algo significativo. Si escribe un código "pequeño, granular" que es fácil de probar correcto, entonces su código consistirá en una gran cantidad de funciones pequeñas, y aún tendrá que determinar si todas funcionan correctamente en concierto.
Dado que las pruebas unitarias son inevitablemente más simples que el código que están probando, es menos probable que tengan errores. Incluso si algunas de las pruebas de su unidad tienen fallas, en general mejorarán la calidad de su base de código principal. (Si las pruebas de su unidad son tan defectuosas que esto no es cierto, entonces es probable que su base de código principal también sea una pila humeante, y está completamente confundido. Creo que todos asumimos un nivel básico de competencia).
Si desea aplicar un segundo nivel de pruebas unitarias para comprobar que las pruebas de su unidad son correctas, puede hacerlo, pero está sujeto a rendimientos decrecientes. Para verlo falsamente numéricamente:
Suponga que las pruebas unitarias reducen el número de errores de producción en un 50%. Luego, escribe pruebas de metaunidades (pruebas unitarias para encontrar errores en las pruebas unitarias). Diga que esto tiene problemas con las pruebas de su unidad, lo que reduce la tasa de errores de producción al 40%. Pero llevó 80% de tiempo escribir las pruebas de metaunidad como lo hizo escribir las pruebas unitarias. Para el 80% del esfuerzo, solo obtuviste otro 20% de la ganancia. Tal vez escribir pruebas de meta-meta-unidades te da otros 5 puntos porcentuales, pero ahora nuevamente tomó el 80% del tiempo que tomó escribir las pruebas de la unidad meta, por lo que el 64% del esfuerzo de escribir las pruebas unitarias (que 50%) obtuviste otro 5%. Incluso con números sustancialmente más liberales, no es una forma eficiente de pasar su tiempo.
En este escenario, está claro que pasar el punto de escribir pruebas unitarias no vale la pena el esfuerzo.
Editar: También me doy cuenta de que podría escribir pequeñas pruebas unitarias granulares que serían fáciles de entender. Sin embargo, si usted supone que el código pequeño y granular es impecable ya prueba de balas, puede escribir programas pequeños y granulares y no necesitar pruebas unitarias.
La idea de las pruebas unitarias es probar las cosas más granulares, luego juntar las pruebas para probar el caso más grande. Si está escribiendo pruebas grandes, pierde un poco de los beneficios allí, aunque es probable que sea más rápido escribir pruebas más grandes.
Las pruebas unitarias son sus requisitos concretizados. No sé ustedes, pero me gusta tener los requisitos especificados antes de comenzar a codificar (TDD). Al escribirlos y tratarlos como cualquier otra parte de su código, comenzará a sentirse seguro presentando nuevas funciones sin romper la funcionalidad anterior. Para garantizar que todo tu código es necesario y que las pruebas realmente prueban el código, utilizo pitest ( existen otras variantes para las pruebas de mutación para otros idiomas). Para mí, código no probado, es un código defectuoso, por limpio que sea.
Si la prueba prueba un código complejo y es compleja, a menudo escribo pruebas para mis pruebas ( ejemplo ).