unit testing - unit - ¿Cómo se mide la calidad de las pruebas de su unidad?
pruebas unitarias php (13)
Si usted (o su organización) aspira a probar a fondo su código, ¿cómo mide el éxito o la calidad de sus esfuerzos?
- ¿Utiliza cobertura de código, a qué porcentaje apunta?
- ¿Encuentra que las filosofías como TDD tienen un mejor impacto que las métricas?
Creo que algunas de las mejores prácticas para pruebas unitarias son:
- Deben ser autónomos, es decir, no requieren demasiada configuración ni dependencias externas para ejecutarse. Permita que las pruebas creen sus propias dependencias, como archivos y sitios web necesarios para que se ejecuten las pruebas.
- Use pruebas unitarias para reproducir errores antes de arreglarlos. Esto ayuda a evitar que los errores vuelvan a aparecer en el futuro.
- Use una herramienta de cobertura de código para detectar el código crítico que no se ejerce mediante pruebas unitarias.
- Integre pruebas unitarias con compilaciones nocturnas y compilaciones de lanzamiento.
- Publique informes de resultados de pruebas e informes de cobertura de códigos en un sitio web donde todos los miembros del equipo puedan examinarlos. La publicación idealmente debe ser automatizada e integrada en el sistema de compilación.
No espere alcanzar el 100% de cobertura de código a menos que desarrolle software de misión crítica. Puede ser muy costoso alcanzar este nivel y, para la mayoría de los proyectos, no valdrá la pena el esfuerzo.
La cobertura del código es para probar, ya que las pruebas se refieren a la programación. Solo puede decirte cuándo hay un problema, no puede decirte cuándo funciona todo. Debe tener una cobertura de código del 100% y más. Las ramificaciones de la lógica del código deben probarse con varios valores de entrada, ejercitando completamente los casos normal, de borde y de esquina.
La cobertura del código es una medida útil, pero debe usarse con cuidado. Algunas personas toman la cobertura del código, especialmente el porcentaje cubierto, un poco demasiado en serio y lo ven como la métrica para una buena prueba unitaria.
Mi experiencia me dice que, más importante que tratar de obtener una cobertura del 100%, lo que no es tan fácil, las personas deberían centrarse en verificar que las secciones críticas estén cubiertas. Pero incluso entonces puede obtener falsos positivos.
Mi sugerencia no es una forma de determinar si tiene buenas pruebas unitarias per se, pero es una forma de hacer crecer un buen conjunto de pruebas con el tiempo.
Cada vez que encuentre un error, ya sea en su desarrollo o informado por otra persona, corríjalo dos veces. Primero crea una prueba unitaria que reproduce el problema. Cuando tienes una prueba fallida, vas y arreglas el problema.
Si hubo un problema en primer lugar, es una pista sobre la sutileza del código o el dominio. Agregar una prueba le permite asegurarse de que nunca vuelva a introducirse en el futuro.
Otro aspecto interesante acerca de este enfoque es que lo ayudará a comprender el problema desde un nivel superior antes de ir a ver las complejidades del código.
Además, +1 por el valor y las dificultades de la cobertura de prueba ya mencionadas por otros.
Monitorear las tasas de cobertura del código puede ser útil, pero en lugar de centrarme en una tasa objetivo arbitraria (80%, 90%, 100%), he encontrado que es útil apuntar a una tendencia positiva en el tiempo.
Normalmente hago TDD, así que primero escribo las pruebas, lo que me ayuda a ver cómo quiero usar los objetos.
Luego, cuando estoy escribiendo las clases, la mayor parte del tiempo puedo detectar errores comunes (es decir, suposiciones que estoy haciendo, por ejemplo, una variable de un tipo particular o rango de valores) y cuando surgen, escribo un prueba específica para ese caso específico.
Aparte de eso, y de obtener la mejor cobertura de código posible (a veces no es posible obtener el 100%), está más o menos listo. Luego, si surge algún error en el futuro, solo asegúrate de escribir un caso de prueba que lo exponga primero, y que pase cuando se solucione. Luego arregla como es normal.
Para obtener una medida completa de confianza en su código, necesita diferentes niveles de prueba: unidad, integración y funcional. Estoy de acuerdo con el consejo anterior que establece que las pruebas deben ser automáticas (integración continua) y que las pruebas unitarias deben cubrir todas las ramas con una variedad de conjuntos de datos de casos extremos. Las herramientas de cobertura de código (por ejemplo, Cobertura, Clover, EMMA, etc.) pueden identificar agujeros en sus ramas, pero no en la calidad de sus conjuntos de datos de prueba. El análisis del código estático como FindBugs, PMD, CPD puede identificar áreas problemáticas en su código antes de que se conviertan en un problema y contribuyan en gran medida a promover mejores prácticas de desarrollo.
Las pruebas deben intentar replicar el entorno general en el que se ejecutará la aplicación tanto como sea posible. Debe comenzar desde el caso más simple posible (unidad) hasta el más complejo (funcional). En el caso de una aplicación web, es imprescindible obtener un proceso automatizado para ejecutar todos los casos de uso de su sitio web con una variedad de navegadores, por lo que debería haber algo similar a SeleniumRC en su conjunto de herramientas.
Sin embargo, el software existe para satisfacer una necesidad comercial, por lo que también hay pruebas contra los requisitos. Esto tiende a ser más un proceso manual basado en pruebas funcionales (web). Básicamente, deberá crear una matriz de rastreabilidad para cada requisito en la especificación y la prueba funcional correspondiente. A medida que se crean pruebas funcionales, se comparan con uno o más requisitos (por ejemplo, Iniciar sesión como Fred, actualizar los detalles de la cuenta para la contraseña, cerrar la sesión nuevamente). Esto aborda la cuestión de si el producto entregable coincide o no con las necesidades del negocio.
En general, recomendaría un enfoque de desarrollo basado en pruebas basado en el sabor de las pruebas unitarias automatizadas (JUnit, nUnit, etc.). Para las pruebas de integración, recomendaría tener una base de datos de prueba que se rellene automáticamente en cada compilación con un conjunto de datos conocido que ilustre los casos de uso comunes, pero que permita la construcción de otras pruebas. Para las pruebas funcionales necesitará algún tipo de robot de interfaz de usuario (SeleniumRC para web, Abbot para Swing, etc.). Las métricas acerca de cada una pueden recopilarse fácilmente durante el proceso de compilación y mostrarse en el servidor de CI (p. Ej., Hudson) para que todos los desarrolladores puedan verlas.
Si se puede romper, debe ser probado. Si se puede probar, debe ser automático.
Si su forma principal de medir la calidad de la prueba es alguna medida automática, ya ha fallado.
Las métricas pueden ser engañosas y se pueden jugar. Y si la métrica es el medio principal (o, peor aún, el único) para juzgar la calidad, se evaluarán (quizás sin intención).
La cobertura del código, por ejemplo, es profundamente engañosa porque la cobertura del código del 100% no está cerca de la cobertura de prueba completa. Además, una cifra como "80% de cobertura de código" es tan engañosa sin contexto. Si esa cobertura se encuentra en las partes más complejas del código y simplemente se pierde el código, que es tan simple que es fácil de verificar a simple vista, es mucho mejor que si esa cobertura está sesgada de forma inversa.
Además, es importante distinguir entre el dominio de prueba de una prueba (su conjunto de características, en esencia) y su calidad. La calidad de la prueba no está determinada por cuánto prueba, así como la calidad del código no está determinada por una lista de características. La calidad de la prueba se determina por el rendimiento de una prueba en las pruebas. Eso es realmente muy difícil de resumir en una métrica automatizada.
La próxima vez que vaya a escribir una prueba unitaria, pruebe este experimento. Vea cuántas formas diferentes puede escribir de modo que tenga la misma cobertura de código y pruebe el mismo código. Vea si es posible escribir una prueba muy pobre que cumpla con estos criterios y una muy buena prueba también. Creo que te sorprenderán los resultados.
En última instancia, no hay sustituto para la experiencia y el juicio. Un ojo humano, con suerte varios ojos, necesita mirar el código de prueba y decidir si es bueno o no.
Soy muy pro-TDD, pero no le doy mucha importancia a las estadísticas de cobertura. Para mí, el equipo de desarrollo siente el éxito y la utilidad de las pruebas unitarias durante un período de tiempo de desarrollo, ya que las pruebas (a) descubren errores por adelantado, (b) permiten la refactorización y el cambio sin regresión, (c) ayudan a diseño modular, desacoplado, (d) y otras cosas.
O, como dijo Martin Fowler, la evidencia anecdótica en apoyo de las pruebas unitarias y TDD es abrumadora, pero no se puede medir la productividad. Lea más en su bliki aquí: http://www.martinfowler.com/bliki/CannotMeasureProductivity.html
Una técnica adicional que trato de usar es dividir tu código en dos partes. Recientemente publiqué sobre esto here . La breve descripción es mantener su código de producción en dos conjuntos de bibliotecas donde un conjunto (con suerte el conjunto más grande) tiene una cobertura de línea del 100% (o mejor si puede medirlo) y el otro (con suerte una pequeña cantidad de código) tiene 0% de cobertura, sí, cero por ciento de cobertura.
Tus diseños deberían permitir esta partición. Esto debería facilitar la visualización del código que no está cubierto. Con el tiempo, puede tener ideas sobre cómo mover el código del conjunto más pequeño al conjunto más grande.
El concepto de prueba de mutación parece prometedor como una forma de medir (¿evaluar?) La calidad de las pruebas de su unidad. Las pruebas de mutación básicamente significan hacer pequeñas "mutaciones" a su código de producción y luego ver si falla alguna prueba de la unidad. Pequeñas mutaciones típicamente significan cambiar and
para o <
a <=
. Si una o más pruebas unitarias fallan, significa que el "mutante" fue capturado. Si el mutante sobrevive al conjunto de pruebas de su unidad, significa que se ha perdido una prueba. Cuando aplico la prueba de mutaciones al código con una cobertura de línea y sucursal del 100%, normalmente encontrará algunos puntos en los que omití las pruebas.
Consulte https://en.wikipedia.org/wiki/Mutation_testing para obtener una descripción del concepto y enlaces a herramientas.
- Además de TDD, me encontré escribiendo pruebas más sensatas desde BDD (por ejemplo, http://rspec.info/ )
- La discusión eterna es siempre burlarse o no burlarse. Algunos simulacros pueden volverse más complejos que el código que está probando (lo que generalmente indica una mala separación de las preocupaciones).
- Por lo tanto, me gusta la idea de tener una métrica como: probar la complejidad por complejidad del código. o simplificado: el número de líneas de prueba por líneas de código.