¿Datos aleatorios en pruebas unitarias?

136

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 solo usa un único valor estático.

Le he dado una serie de razones diferentes en contra de esto, las principales son:

  • valores aleatorios significa 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 yb) obligarnos a probar ese valor cada vez, por lo que sabemos que funciona, pero como es aleatorio, no sabemos cuál fue 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 usan para eliminar 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 hacerlo?

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

Adam V
fuente
32
"valores aleatorios significa que la prueba no es realmente repetible" no es cierto, ya que las pruebas utilizarán números pseudoaleatorios. Proporcione la misma semilla inicial, obtenga la misma secuencia de pruebas "aleatorias".
Raedwald
11
Anécdota: una vez escribí una clase de exportación CSV, y las pruebas aleatorias revelaron un error cuando los caracteres de control se colocaron al final de una celda. Sin pruebas aleatorias, nunca hubiera pensado agregar eso como un caso de prueba. ¿Siempre fallaba? No. ¿Es una prueba perfecta? No. ¿Me ayudó a atrapar y corregir un error? Si.
Tyzoid
1
Las pruebas también pueden servir como documentación, para explicar cuándo el código espera como entrada y qué se espera como salida. Tener una prueba con datos arbitrarios claros puede ser más simple y más explicativo que el código que genera datos aleatorios.
splintor
Si su prueba unitaria falla debido a un valor generado aleatoriamente, y este valor no es parte de una afirmación, buena suerte con la depuración de su prueba unitaria.
eriksmith200

Respuestas:

72

Hay un compromiso. Tu compañero de trabajo está realmente en algo, 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í mismo es evidencia de esa hipótesis. Lo que deberían ser las pruebas unitarias es un intento de proporcionar pruebas contrarias para refutar que el programa funciona según las especificaciones.

Ahora, puede escribir las pruebas unitarias a mano, pero realmente 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 muchas pruebas unitarias que intentan romper su código.

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

Java http://functionaljava.org/

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

Haskell http://www.cs.chalmers.se/~rjmh/QuickCheck/

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

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

¡Feliz prueba!

Apocalipsis
fuente
1
+1 a esto. ScalaCheck realiza un trabajo fenomenal al generar datos de prueba aleatorios minimizados de manera repetible.
Daniel Spiewak
17
No es al azar. Es arbitrario. Gran diferencia :)
Apocalisp
reductiotest.org ahora parece existir por más tiempo, y Google no me señaló en ningún otro lado. ¿Alguna idea de dónde está ahora?
Raedwald
Ahora es parte de la biblioteca funcional de Java. Enlace editado. Pero solo usaría Scalacheck para probar el código Java.
Apocalisp
ScalaCheck se ha trasladado a GitHub. Enlace actualizado en respuesta. También es útil para Java, no solo Scala. (El enlace anterior era code.google.com/p/scalacheck )
RobertB
38

Este tipo de prueba se llama prueba de mono . Cuando se hace bien, puede eliminar los 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 sondea para toda la familia del error específico; e incluir en la prueba de la unidad la entrada específica (de los datos aleatorios) que causó la falla inicial.

Dragón plateado
fuente
26

Aquí hay una casa a mitad de camino que tiene algún uso, que es sembrar tu PRNG con una constante. Eso le permite generar datos 'aleatorios' que son repetibles.

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

Will Dean
fuente
44
He visto que esto funciona bien en un sistema que tenía muchos bloqueos e hilos. La semilla 'aleatoria' se escribió en un archivo en cada ejecución, luego, si una ejecución fallaba, podríamos resolver la ruta que tomó el código y escribir una prueba unitaria escrita a mano para ese caso que habíamos perdido.
Ian Ringrose el
¿Qué significa PRNG?
systemovich
Pseudo Random Number Generator
Will Dean
16

En el libro Beautiful Code , hay un capítulo llamado "Beautiful Tests", donde pasa por una estrategia de prueba para el algoritmo de Búsqueda Binaria . Un párrafo se llama "Actos de prueba aleatorios", en el que crea matrices aleatorias para probar a fondo 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 las pruebas es una opción viable.

mreggen
fuente
16

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

Ejecutar localmente (por ejemplo, durante la noche en su caja de desarrollo) las pruebas aleatorias han encontrado errores obvios y oscuros. Los oscuros son lo suficientemente arcanos que creo que las pruebas aleatorias fueron realmente las únicas realistas para eliminarlas. Como prueba, tomé un error difícil de encontrar descubierto a través de pruebas aleatorias e hice que media docena de desarrolladores de crack revisaran la función (aproximadamente una docena de líneas de código) donde ocurrió. Ninguno pudo detectarlo.

Muchos de sus argumentos en contra de datos aleatorios son sabores de "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 a mano, esto le permite crear trivialmente una nueva prueba que prueba el problema específico al codificar la semilla para esa prueba. Por supuesto, probablemente sea mejor codificar a mano una prueba explícita que cubra ese caso, pero la pereza tiene sus virtudes, y esto incluso le permite generar automáticamente nuevos casos de prueba a partir de una semilla que falla.

Sin embargo, el único punto que haces que no puedo debatir es que rompe los sistemas de compilación. La mayoría de las pruebas de integración continua y de compilación esperan que las pruebas hagan lo mismo siempre. Por lo tanto, una prueba que falla aleatoriamente creará caos, fallando aleatoriamente y señalando con el dedo los cambios que son inofensivos.

Entonces, una solución es ejecutar sus 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 un montón del espacio de entrada (si lo ejecuta para varias 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 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 mayor cobertura 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ñala con el dedo un cambio particular cuando las cosas fallan).

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

BeeOnRope
fuente
14

Si está haciendo TDD, entonces diría que los datos aleatorios son un enfoque excelente. Si su prueba se escribe con constantes, solo puede garantizar que su código funcione para el valor específico. Si su prueba falla al azar 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, ¿la presencia de constantes no implica que solo necesita trabajar para esas constantes?

Estoy exagerando, sin embargo, prefiero inyectar datos aleatorios en mi prueba como una 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, luego bifurque su prueba en función de esa variable, entonces eso es un olor.

Jimmy Bosse
fuente
10

Una ventaja para alguien que mira las pruebas es que los datos arbitrarios claramente no son importantes. He visto demasiadas pruebas que involucraron docenas de datos y puede ser difícil saber qué debe ser así y qué sucede de esa manera. Por ejemplo, si un método de validación de dirección se prueba con un código postal específico y todos los demás datos son aleatorios, puede estar seguro de que el código postal es la única parte importante.

Trystan Spangler
fuente
9
  • Si es un valor aleatorio y la prueba falla, necesitamos a) arreglar el objeto yb) obligarnos a probar ese valor cada vez, por lo que sabemos que funciona, pero como es aleatorio, no sabemos cuál fue el valor

Si su caso de prueba no registra con precisión lo que está probando, tal vez necesite volver a codificar el caso de prueba. Siempre quiero tener registros a los que pueda referirme para ver los casos de prueba, de modo que sepa exactamente qué causó la falla, ya sea utilizando datos estáticos o aleatorios.

EBGreen
fuente
9

Su compañero de trabajo está haciendo pruebas de fuzz , aunque no lo sabe. Son especialmente valiosos en los sistemas de servidor.

Robert Gould
fuente
2
¿Pero no es esto algo fundamentalmente diferente de las pruebas unitarias? y hecho en un momento diferente?
endolito
1
@endolith no hay ninguna ley de la física que obliga a ejecutar las pruebas en determinados momentos
user253751
1
@immibis Pero hay buenas razones para hacer pruebas particulares en momentos particulares. No ejecuta una batería de pruebas unitarias cada vez que un usuario hace clic en el botón "Aceptar".
endolito el
5

¿Puede generar algunos datos aleatorios una vez (quiero decir exactamente una vez, no una vez por prueba) y luego usarlos en todas las pruebas posteriores?

Definitivamente puedo ver el valor en la creación de datos aleatorios para probar esos casos en los que no ha pensado, pero tiene razón, tener pruebas unitarias que pueden pasar o fallar al azar es algo malo.

Programador ilegal
fuente
5

Deben preguntarse cuál es el objetivo de su prueba.
Las pruebas unitarias tratan de verificar la lógica, el flujo de código y las interacciones de objetos. El uso de valores aleatorios trata de 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 puedo recordar ni una vez que este método fue exitoso para encontrar problemas. Pero he visto muchos problemas de determinismo (en las pruebas) tratando de ser inteligente con valores aleatorios y principalmente con fechas aleatorias.
La prueba fuzz es un enfoque válido para las pruebas de integración y las pruebas de extremo a extremo .

Ohad Bruker
fuente
Agregaría que usar entrada aleatoria para fuzzing es un mal sustituto de fuzzing guiado por cobertura cuando es posible.
gobenji
1

Si está utilizando una entrada aleatoria para sus pruebas, necesita registrar las entradas para poder ver cuáles son los valores. De esta manera, si se encuentra con algún caso límite, puede escribir la prueba para reproducirlo. He escuchado las mismas razones de las personas para no usar la entrada aleatoria, pero una vez que tiene una idea de los valores reales utilizados para una ejecución de prueba en particular, no es un problema tan importante.

La noción de datos "arbitrarios" también es muy útil como una forma de significar algo que no es importante. Tenemos algunas pruebas de aceptación que vienen a la mente cuando hay muchos datos de ruido que no son relevantes para la prueba en cuestión.

craigb
fuente
0

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

EBGreen
fuente
0

Nos topamos con esto hoy. Quería pseudoaleatorio (por lo que se vería como datos de audio comprimido en términos de tamaño). TODO que también quería determinista . rand () era diferente en OSX que en Linux. Y a menos que volviera a sembrar, podría cambiar en cualquier momento. Así que lo cambiamos para que fuera determinista pero aún psuedo-aleatorio: la prueba es repetible, tanto como el uso de datos enlatados (pero más convenientemente escritos).

Esto NO fue probado por alguna fuerza bruta al azar a través de rutas de código. Esa es la diferencia: sigue siendo determinista, sigue siendo repetible, sigue utilizando 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 es al azar? Hablemos de cerveza. :-)

Tim James
fuente
0

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

  • Prueba con datos fijos
  • Prueba con datos aleatorios
  • Genere datos aleatorios una vez , luego úselos como 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 genera solo una vez. Luego escriba un conjunto de pruebas aleatorias que ejecute también .

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

Tom W
fuente
-2

¿Cómo puede su chico ejecutar la prueba nuevamente cuando no ha podido ver si la ha solucionado? Es decir, pierde la repetibilidad de las pruebas.

Si bien creo que probablemente haya algún valor en arrojar una carga de datos aleatorios en las pruebas, como se menciona en otras respuestas, cae más bajo el título de pruebas de carga que cualquier otra cosa. Es prácticamente una práctica de "prueba por esperanza". Creo que, en realidad, tu chico simplemente no está pensando en lo que está tratando de probar, y compensar esa falta de pensamiento esperando que la aleatoriedad eventualmente atrape algún error misterioso.

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

Greg Whitfield
fuente
3
Es posible registrar datos aleatorios o semillas aleatorias para que la prueba pueda reproducirse.
cbp