Pregunta

Tengo un compañero de trabajo que escribe pruebas unitarias para objetos que llenan sus campos con datos aleatorios.Su razón es que ofrece una gama más amplia de pruebas, ya que probará muchos valores diferentes, mientras que una prueba normal sólo utiliza un único valor estático.

Le he dado varias razones diferentes en contra de esto, siendo las principales:

  • los valores aleatorios significan que la prueba no es realmente repetible (lo que también significa que si la prueba puede fallar aleatoriamente, puede hacerlo en el servidor de compilación y romper la compilación)
  • Si es un valor aleatorio y la prueba falla, necesitamos a) arreglar el objeto y b) obligarnos a probar ese valor cada vez, para que sepamos que funciona, pero como es aleatorio no sabemos cuál era el valor.

Otro compañero de trabajo agregó:

  • Si estoy probando una excepción, los valores aleatorios no garantizarán que la prueba termine en el estado esperado.
  • Los datos aleatorios se utilizan para limpiar un sistema y realizar pruebas de carga, no para pruebas unitarias.

¿Alguien más puede agregar razones adicionales que pueda darle para que deje de hacer esto?

(O alternativamente, ¿es este un método aceptable para escribir pruebas unitarias y mi otro compañero de trabajo y yo estamos equivocados?)

¿Fue útil?

Solución

Hay un compromiso.Tu compañero de trabajo realmente tiene algo en mente, pero creo que lo está haciendo mal.No estoy seguro de que las pruebas totalmente aleatorias sean muy útiles, pero ciertamente no son inválidas.

La especificación de un programa (o unidad) es una hipótesis de que existe algún programa que la cumple.El programa en sí es entonces evidencia de esa hipótesis.Lo que deberían ser las pruebas unitarias es un intento de proporcionar evidencia contraria para refutar que el programa funciona de acuerdo con las especificaciones.

Ahora puedes escribir las pruebas unitarias a mano, pero en realidad es una tarea mecánica.Se puede automatizar.Todo lo que tiene que hacer es escribir la especificación y una máquina puede generar muchísimas pruebas unitarias que intentan descifrar su código.

No sé qué idioma estás usando, pero mira aquí:

Javahttp://funcionaljava.org/

Scala (o Java)http://github.com/rickynils/scalacheck

Haskellhttp://www.cs.chalmers.se/~rjmh/QuickCheck/

.NETO:http://blogs.msdn.com/dsyme/archive/2008/08/09/fscheck-0-2.aspx

Estas herramientas tomarán como entrada sus especificaciones bien formadas y generarán automáticamente tantas pruebas unitarias como desee, con datos generados automáticamente.Utilizan estrategias de "reducción" (que usted puede modificar) para encontrar el caso de prueba más simple posible para descifrar su código y asegurarse de que cubra bien los casos extremos.

¡Feliz prueba!

Otros consejos

Este tipo de prueba se llama prueba de mono.Cuando se hace correctamente, puede eliminar insectos de los rincones realmente oscuros.

Para abordar sus inquietudes sobre la reproducibilidad:La forma correcta de abordar esto es registrar las entradas de prueba fallidas, generar una prueba unitaria, que sondee las toda la familia del error específico;e incluir en la prueba unitaria la entrada específica (de los datos aleatorios) que causó la falla inicial.

Aquí hay un punto intermedio que tiene algún uso, que es sembrar su PRNG con una constante.Eso le permite generar datos "aleatorios" que son repetibles.

Personalmente, creo que hay lugares donde los datos aleatorios (constantes) son útiles en las pruebas: después de pensar que has hecho todos los rincones cuidadosamente pensados, el uso de estímulos de un PRNG a veces puede encontrar otras cosas.

En el libro Hermoso código, hay un capítulo llamado "Pruebas hermosas", donde explica una estrategia de prueba para el Búsqueda binaria algoritmo.Un párrafo se llama "Actos de prueba aleatorios", en el que crea matrices aleatorias para probar exhaustivamente el algoritmo.Puede leer algo de esto en línea en Google Books, página 95, pero es un gran libro que vale la pena tener.

Básicamente, esto solo muestra que generar datos aleatorios para realizar pruebas es una opción viable.

Una ventaja para quien observa las pruebas es que los datos arbitrarios claramente no son importantes.He visto demasiadas pruebas que involucran docenas de datos y puede ser difícil decir qué debe ser de esa manera y qué es así.P.ej.Si se prueba un método de validación de direcciones con un código postal específico y todos los demás datos son aleatorios, entonces puede estar bastante seguro de que el código postal es la única parte importante.

Si está haciendo TDD, yo diría que los datos aleatorios son un enfoque excelente.Si su prueba está escrita con constantes, entonces solo puede garantizar que su código funcione para el valor específico.Si su prueba falla aleatoriamente en el servidor de compilación, es probable que haya un problema con la forma en que se escribió la prueba.

Los datos aleatorios ayudarán a garantizar que cualquier refactorización futura no dependa de una constante mágica.Después de todo, si sus pruebas son su documentación, ¿no implica la presencia de constantes que solo necesita funcionar para esas constantes?

Estoy exagerando, sin embargo, prefiero inyectar datos aleatorios en mi prueba como señal de que "el valor de esta variable no debería afectar el resultado de esta prueba".

Sin embargo, diré que si usa una variable aleatoria y luego bifurca su prueba en función de esa variable, entonces eso es un olor.

  • Si es un valor aleatorio y la prueba falla, necesitamos a) arreglar el objeto y b) obligarnos a probar ese valor cada vez, para que sepamos que funciona, pero como es aleatorio no sabemos cuál era el valor.

Si su caso de prueba no registra con precisión lo que está probando, tal vez necesite recodificar el caso de prueba.Siempre quiero tener registros a los que pueda consultar los casos de prueba para saber exactamente qué causó que fallara, ya sea que use datos estáticos o aleatorios.

Tu compañero de trabajo está haciendo prueba de fuzz, aunque él no lo sabe.Son especialmente valiosos en sistemas de servidores.

Estoy a favor de las pruebas aleatorias y las escribo.Sin embargo, si son apropiados en un entorno de construcción particular y en qué conjuntos de pruebas deberían incluirse es una cuestión más matizada.

Las pruebas aleatorias ejecutadas localmente (por ejemplo, durante la noche en su caja de desarrollo) han encontrado errores tanto obvios como oscuros.Los oscuros son lo suficientemente arcanos que creo que las pruebas aleatorias fueron realmente la única opción realista para eliminarlos.Como prueba, tomé un error difícil de encontrar descubierto mediante pruebas aleatorias e hice que media docena de desarrolladores de crack revisaran la función (alrededor de una docena de líneas de código) donde ocurrió.Ninguno fue capaz de detectarlo.

Muchos de sus argumentos en contra de los datos aleatorios son del tipo "la prueba no es reproducible".Sin embargo, una prueba aleatoria bien escrita capturará la semilla utilizada para iniciar la semilla aleatoria y la generará en caso de falla.Además de permitirle repetir la prueba manualmente, esto le permite crear trivialmente nuevas pruebas que prueben el problema específico codificando la semilla para esa prueba.Por supuesto, probablemente sea mejor codificar manualmente una prueba explícita que cubra ese caso, pero la pereza tiene sus virtudes, y esto incluso le permite esencialmente generar automáticamente nuevos casos de prueba a partir de una semilla fallida.

Sin embargo, el único punto que usted señala que no puedo debatir es que rompe los sistemas de construcción.La mayoría de las pruebas de integración continua y de compilación esperan que las pruebas hagan lo mismo en todo momento.Entonces, una prueba que falla aleatoriamente creará caos, fallará aleatoriamente y señalará con el dedo cambios que eran inofensivos.

Entonces, una solución es seguir ejecutando las pruebas aleatorias como parte de las pruebas de compilación y CI, pero ejecutarlo con una semilla fija, para un número fijo de iteraciones.Por lo tanto, la prueba siempre hace lo mismo, pero aún explora una gran parte del espacio de entrada (si la ejecuta durante múltiples iteraciones).

Localmente, por ejemplo, al cambiar la clase en cuestión, puede ejecutarla para más iteraciones o con otras semillas.Si las pruebas aleatorias alguna vez se vuelven más populares, incluso se podría imaginar un conjunto específico de pruebas que se sabe que son aleatorias, que podrían ejecutarse con diferentes semillas (por lo tanto, con una cobertura cada vez mayor a lo largo del tiempo) y donde las fallas no significarían lo mismo. como sistemas de CI deterministas (es decir, las ejecuciones no están asociadas 1:1 con cambios de código y, por lo tanto, no se señala con el dedo un cambio en particular cuando las cosas fallan).

Hay mucho que decir sobre las pruebas aleatorias, especialmente las bien escritas, ¡así que no se apresure a descartarlas!

¿Puede generar algunos datos aleatorios una vez (me refiero exactamente una vez, no una vez por ejecución de prueba) y luego usarlos en todas las pruebas posteriores?

Definitivamente puedo ver el valor de crear datos aleatorios para probar aquellos casos en los que no has pensado, pero tienes razón, tener pruebas unitarias que puedan pasar o fallar aleatoriamente es algo malo.

Deberían preguntarse cuál es el objetivo de su prueba.
Pruebas unitarias tratan de verificar la lógica, el flujo de código y las interacciones de objetos.El uso de valores aleatorios intenta lograr un objetivo diferente, lo que reduce el enfoque y la simplicidad de la prueba.Es aceptable por razones de legibilidad (generación de UUID, identificadores, claves, etc.).
Específicamente para las pruebas unitarias, no recuerdo ni siquiera una vez que este método haya tenido éxito al encontrar problemas.Pero he visto muchos problemas de determinismo (en las pruebas) tratando de ser inteligente con valores aleatorios y principalmente con fechas aleatorias.
Las pruebas de fuzz son un enfoque válido para pruebas de integracion y pruebas de extremo a extremo.

Si está utilizando entradas aleatorias para sus pruebas, debe registrar las entradas para poder ver cuáles son los valores.De esta manera, si se encuentra con algún caso extremo, podrá poder Escribe la prueba para reproducirla.He escuchado las mismas razones de personas para no usar entradas aleatorias, pero una vez que tienes una idea de los valores reales utilizados para una ejecución de prueba en particular, entonces no es un gran problema.

La noción de datos "arbitrarios" también es muy útil como forma de significar algo que es no importante.Se nos ocurren algunas pruebas de aceptación en las que hay muchos datos de ruido que no son relevantes para la prueba en cuestión.

Dependiendo de su objeto/aplicación, los datos aleatorios tendrían un lugar en las pruebas de carga.Creo que sería más importante utilizar datos que prueben explícitamente las condiciones de contorno de los datos.

Nos topamos con esto hoy.quise pseudoaleatorio (por lo que parecerían datos de audio comprimidos en términos de tamaño).Hice TODO lo que también quería determinista.rand() era diferente en OSX que en Linux.Y a menos que vuelva a sembrar, podría cambiar en cualquier momento.Así que lo cambiamos para que fuera determinista pero aún pseudoaleatorio:la prueba es repetible, tanto como el uso de datos almacenados (pero escritos de manera más conveniente).

Esto era NO pruebas mediante fuerza bruta aleatoria a través de rutas de código.Esa es la diferencia:todavía determinista, aún repetible, todavía usando datos que parecen entradas reales para ejecutar un conjunto de comprobaciones interesantes en casos extremos en lógica compleja.Todavía pruebas unitarias.

¿Eso todavía califica como aleatorio?Hablemos mientras tomamos una cerveza.:-)

Puedo imaginar tres soluciones al problema de los datos de prueba:

  • Prueba con datos fijos
  • Prueba con datos aleatorios
  • Generar datos aleatorios una vez, luego úsalo como tus datos fijos

recomendaría hacer todo lo anterior.Es decir, escriba pruebas unitarias repetibles con algunos casos extremos resueltos utilizando su cerebro y algunos datos aleatorios que genere solo una vez.Luego escribe un conjunto de pruebas aleatorias que ejecutes también.

Nunca se debe esperar que las pruebas aleatorias detecten algo que las pruebas repetibles no detectan.Debe intentar cubrir todo con pruebas repetibles y considerar las pruebas aleatorias como una ventaja.Si encuentran algo, debería ser algo que no se podría haber predicho razonablemente;un verdadero bicho raro.

¿Cómo puede su chico ejecutar la prueba nuevamente cuando no pudo ver si lo solucionó?Es decir.pierde la repetibilidad de las pruebas.

Si bien creo que probablemente tenga algún valor arrojar una carga de datos aleatorios en las pruebas, como se mencionó en otras respuestas, se incluye más bajo el título de pruebas de carga que cualquier otra cosa.Es más o menos una práctica de "probar con esperanza".Creo que, en realidad, tu chico simplemente no está pensando en lo que está tratando de probar y compensa esa falta de pensamiento con la esperanza de que la aleatoriedad eventualmente atrape algún error misterioso.

Entonces el argumento que usaría con él es que está siendo un vago.O, para decirlo de otra manera, si no se toma el tiempo para comprender lo que está tratando de probar, probablemente muestre que realmente no comprende el código que está escribiendo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top