Activado por este hilo , (nuevamente) estoy pensando finalmente en usar pruebas unitarias en mis proyectos. Algunos carteles dicen algo así como "Las pruebas son geniales, si son buenas pruebas". Mi pregunta ahora: ¿Qué son las pruebas "buenas"?
En mis aplicaciones, la parte principal a menudo es algún tipo de análisis numérico, que depende de grandes cantidades de datos observados y da como resultado una función de ajuste que puede usarse para modelar estos datos. Me resultó especialmente difícil construir pruebas para estos métodos, ya que el número de posibles entradas y resultados es demasiado grande para probar cada caso, y los métodos en sí mismos son a menudo bastante largos y no se pueden refactorizar fácilmente sin sacrificar el rendimiento. Estoy especialmente interesado en pruebas "buenas" para este tipo de método.
fuente
Respuestas:
El arte de las pruebas unitarias tiene lo siguiente que decir sobre las pruebas unitarias:
y luego agrega que debería ser completamente automatizado, confiable, legible y mantenible.
Recomiendo leer este libro si aún no lo ha hecho.
En mi opinión, todos estos son muy importantes, pero los tres últimos (confiables, legibles y mantenibles) especialmente, ya que si sus pruebas tienen estas tres propiedades, su código generalmente también las tiene.
fuente
It should run at the push of a button
, ¿eso significa que una prueba unitaria no debería requerir que se ejecuten contenedores (servidor de aplicaciones) (para la unidad que se está probando) o una conexión de recursos (como DB, servicios web externos, etc.)? Estoy confundido acerca de qué partes de una aplicación deben ser probadas y cuáles no. Me han dicho que las pruebas unitarias no deberían depender de la conexión a la base de datos y de los contenedores en ejecución, y tal vez usaron maquetas en su lugar.Una buena prueba unitaria no refleja la función que está probando.
Como un ejemplo muy simplificado, considere que tiene una función que devuelve un promedio de dos int. La prueba más completa llamaría a la función y verificaría si un resultado es en realidad un promedio. Esto no tiene ningún sentido: está reflejando (replicando) la funcionalidad que está probando. Si cometió un error en la función principal, cometerá el mismo error en la prueba.
En otras palabras, si te encuentras replicando la funcionalidad principal en la prueba de la unidad, es una señal probable de que estás perdiendo el tiempo.
fuente
Las buenas pruebas unitarias son esencialmente la especificación en forma ejecutable:
He descubierto que Test-Driven-Development es muy adecuado para las rutinas de la biblioteca, ya que esencialmente escribe primero la API y, luego, la implementación real.
fuente
para TDD, las "buenas" pruebas prueban las características que el cliente desea ; las características no se corresponden necesariamente con las funciones, y el desarrollador no debe crear escenarios de prueba en el vacío
en su caso, supongo, la 'característica' es que la función de ajuste modela los datos de entrada dentro de una cierta tolerancia a errores. Como no tengo idea de lo que realmente estás haciendo, estoy inventando algo; Ojalá sea análogo.
Ejemplo de historia:
Entonces vaya a hablar con los pilotos (y con la computadora de orientación, si es consciente). Primero hablas de lo que es "normal", luego hablas de lo anormal. Descubre lo que realmente importa en este escenario, lo que es común, lo que es poco probable y lo que es simplemente posible.
Digamos que normalmente tendrá una ventana de medio segundo sobre siete canales de datos de telemetría: velocidad, cabeceo, balanceo, guiñada, vector objetivo, tamaño objetivo y velocidad objetivo, y que estos valores serán constantes o cambiarán linealmente. Anormalmente puede tener menos canales y / o los valores pueden estar cambiando rápidamente. Así que juntos se les ocurren algunas pruebas como:
Ahora, es posible que haya notado que no hay un escenario para la situación particular descrita en la historia. Resulta que, después de hablar con el cliente y otras partes interesadas, ese objetivo en la historia original era solo un ejemplo hipotético. Las pruebas reales salieron de la discusión que siguió. Esto puede suceder. La historia debe reescribirse, pero no tiene que ser así [ya que la historia es solo un marcador de posición para una conversación con el cliente].
fuente
Cree pruebas para casos de esquina, como un conjunto de pruebas que contenga solo el número mínimo de entradas (posible 1 o 0) y algunos casos estándar. Esas pruebas unitarias no son un reemplazo para las pruebas de aceptación exhaustivas, ni deberían serlo.
fuente
He visto muchos casos en los que las personas invierten una enorme cantidad de esfuerzo escribiendo pruebas para el código que rara vez se ingresa, y no escribiendo pruebas para el código que se ingresa con frecuencia.
Antes de sentarse a escribir cualquier prueba, debe mirar algún tipo de gráfico de llamadas para asegurarse de planificar una cobertura adecuada.
Además, no creo en escribir pruebas solo por decir "Sí, lo probamos". Si estoy usando una biblioteca que se deja caer y permanecerá inmutable, no voy a perder el día escribiendo pruebas para asegurarme de que las entrañas de una API que nunca cambiará funciona como se esperaba, incluso si ciertas partes de ella califican alto en un gráfico de llamadas. Las pruebas que consumen dicha biblioteca (mi propio código) señalan esto.
fuente
No del todo TDD, pero después de haber entrado en QA, puede mejorar sus pruebas configurando casos de prueba para reproducir cualquier error que surja durante el proceso de QA. Esto puede ser particularmente valioso cuando se busca asistencia a más largo plazo y se comienza a llegar a un lugar donde se arriesga a que las personas reintroduzcan errores viejos sin darse cuenta. Tener una prueba para capturar eso es particularmente valioso.
fuente
Intento que cada prueba solo pruebe una cosa. Intento dar a cada prueba un nombre como shouldDoSomething (). Intento probar el comportamiento, no la implementación. Solo pruebo métodos públicos.
Por lo general, tengo una o algunas pruebas de éxito, y luego tal vez un puñado de pruebas de fracaso, por método público.
Yo uso muchas maquetas. Un buen marco simulado probablemente sería bastante útil, como PowerMock. Aunque todavía no estoy usando ninguno.
Si la clase A usa otra clase B, agregaría una interfaz, X, para que A no use B directamente. Luego crearía una maqueta XMockup y la usaría en lugar de B en mis pruebas. Realmente ayuda a acelerar la ejecución de la prueba, reduciendo la complejidad de la prueba, y también reduce el número de pruebas que escribo para A ya que no tengo que hacer frente a las peculiaridades de B. Puedo, por ejemplo, probar que A llama a X.someMethod () en lugar de un efecto secundario de llamar a B.someMethod ().
Mantenga su código de prueba limpio también.
Al usar una API, como una capa de base de datos, me burlaría de ella y permitiría que la maqueta arroje una excepción en cada oportunidad posible en el comando. Luego ejecuto las pruebas una sin lanzar, y en un bucle, cada vez que lanzo una excepción en la próxima oportunidad hasta que la prueba tenga éxito nuevamente. Un poco como las pruebas de memoria disponibles para Symbian.
fuente
Veo que Andry Lowry ya ha publicado las métricas de prueba de unidad de Roy Osherove; pero parece que nadie ha presentado el conjunto (complementario) que ofrece el tío Bob en Clean Code (132-133). Él usa el acrónimo PRIMERO (aquí con mis resúmenes):
fuente