¿Datos aleatorios en pruebas unitarias?

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á una gran cantidad de 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 comstackción y romper la comstackción)
  • si es un valor aleatorio y la prueba falla, necesitamos a) arreglar el objeto yb) obligarnos a probar ese valor cada vez, para que sepamos que funciona, pero dado que 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
  • datos aleatorios se utilizan para eliminar un sistema y realizar pruebas de carga, no para pruebas unitarias

¿Alguien más puede agregar razones adicionales que le puedo dar para que deje de hacer esto?

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

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.

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

Ahora puede escribir las pruebas unitarias a mano, pero realmente es una tarea mecánica. Puede ser automatizado Todo lo que tiene que hacer es escribir las especificaciones, y una máquina puede generar muchas pruebas unitarias que intenten 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 sus especificaciones bien formadas como entrada y generarán automáticamente tantas pruebas de unidades como desee, con datos generados automáticamente. Utilizan estrategias de “contracción” (que pueden modificarse) 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!

Este tipo de prueba se llama prueba de mono . Cuando se hace bien, puede expulsar insectos de las esquinas realmente oscuras.

Para abordar sus inquietudes sobre la reproducibilidad: la forma correcta de abordar esto, es registrar las entradas de prueba fallidas, generar una prueba de unidad, que sondea para 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 una casa intermedia 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 donde los datos aleatorios (constantes) son útiles para las pruebas: después de pensar que ya ha hecho todas las esquinas cuidadosamente pensadas, el uso de estímulos de un PRNG a veces puede encontrar otras cosas.

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 aleatorios de prueba”, 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 la generación de datos aleatorios para las pruebas es una opción viable.

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

Los datos aleatorios ayudarán a asegurar que cualquier refactorización futura no dependa de una constante mágica. Después de todo, si sus pruebas son su documentación, ¿entonces la presencia de constantes 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 usas una variable aleatoria y luego bifurca tu prueba en función de esa variable, entonces eso es un olor.

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 decir qué debe ser de esa manera y qué sucede de esa manera. Por ejemplo, si un método de validación de direcciones se prueba con un código postal específico y todos los demás datos son aleatorios, entonces puede estar seguro de que el código postal es la única parte importante.

  • si es un valor aleatorio y la prueba falla, necesitamos a) arreglar el objeto yb) obligarnos a probar ese valor cada vez, para que sepamos que funciona, pero dado que 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 recodificar el caso de prueba. Siempre quiero tener registros a los que pueda recurrir para casos de prueba, de modo que sé exactamente qué causó que fallara al usar datos estáticos o aleatorios.

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

Estoy a favor de las pruebas aleatorias, y las escribo. Sin embargo, si son apropiados en un ambiente de construcción particular y en qué suites de prueba deben incluirse es una pregunta más matizada.

Ejecutar localmente (por ejemplo, durante la noche en su cuadro de desarrollo) pruebas aleatorias han encontrado errores obvios y oscuros. Los oscuros son lo suficientemente misteriosos que creo que las pruebas aleatorias fueron en realidad las únicas realistas para expulsarlos. Como prueba, tomé un error difícil de encontrar descubierto mediante pruebas aleatorias y pedí a media docena de desarrolladores de crack que 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 contra los datos aleatorios son sabores de “la prueba no es reproducible”. Sin embargo, una prueba aleatorizada bien escrita capturará la semilla utilizada para iniciar la semilla aleatorizada y la emitirá en caso de falla. Además de permitirle repetir la prueba a mano, esto le permite crear trivialmente nuevas pruebas que prueban el problema específico al codificar 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 generar automáticamente nuevos casos de prueba de una semilla que falla.

Sin embargo, el único punto que usted hace que no puedo debatir es que rompe los sistemas de comstackción. La mayoría de las pruebas de comstackción y de integración continua esperan que las pruebas hagan lo mismo, todo el tiempo. Por lo tanto, una prueba que fracasa al azar creará caos, fallará al azar y señalará los dedos con cambios que son inofensivos.

Entonces, una solución es ejecutar las pruebas aleatorias como parte de las pruebas de comstackción y de CI, pero ejecutarlas 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 múltiples iteraciones).

Localmente, por ejemplo, al cambiar la clase en cuestión, puede ejecutarlo para más iteraciones o con otras semillas. Si las pruebas aleatorias se vuelven cada vez más populares, incluso podría imaginarse 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 creciente a lo largo del tiempo) y donde las fallas no significarían lo mismo. como sistemas deterministas de CI (es decir, las ejecuciones no están asociadas 1: 1 con los cambios de código y, por lo tanto, no 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 te desanimes demasiado!

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

Definitivamente puedo ver el valor de crear datos aleatorios para probar esos 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 objective de su prueba.
Las pruebas unitarias consisten en verificar la lógica, el flujo de código y las interacciones entre objetos. El uso de valores aleatorios trata de lograr un objective diferente, por lo tanto reduce el enfoque y la simplicidad de la prueba. Es aceptable por razones de legibilidad (generación de UUID, ids, claves, etc.).
Específicamente para pruebas unitarias, no puedo recordar ni 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.
La prueba de Fuzz es un enfoque válido para pruebas de integración y pruebas de extremo a extremo .

Si está utilizando una entrada aleatoria para sus pruebas, debe registrar las entradas para que pueda ver cuáles son los valores. De esta forma, si encuentra algún caso de borde, puede escribir la prueba para reproducirlo. He escuchado las mismas razones de las personas por no usar entradas aleatorias, pero una vez que tiene una idea de los valores reales utilizados para una prueba en particular, entonces no es un gran problema.

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 nos vienen a la mente cuando hay una gran cantidad de 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 la prueba de carga. Creo que más importante sería usar datos que prueben explícitamente las condiciones de frontera de los datos.

Acabamos de encontrarnos con esto hoy. Quería seudoaleatorio (por lo que se vería como datos de audio comprimidos en términos de tamaño). Hice TODO eso 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. Entonces lo cambiamos por determinista, pero sigue siendo psuedo-aleatorio: la prueba es repetible, tanto como el uso de datos enlatados (pero escrito de manera más conveniente).

Esto NO fue probado por alguna fuerza bruta al azar a través de rutas de código. Esa es la diferencia: aún determinista, aún repetible, sigue utilizando datos que parecen entradas reales para ejecutar un conjunto de comprobaciones interesantes en casos extremos en lógica compleja. Aún pruebas unitarias.

¿Eso todavía califica es aleatorio? Hablemos de cerveza. 🙂

Puedo imaginar tres soluciones para el problema de los datos de prueba:

  • Prueba con datos fijos
  • Prueba con datos aleatorios
  • Genere datos aleatorios una vez , luego úselos como datos fijos

Yo recomendaría hacer todo lo anterior . Es decir, escribir pruebas unitarias repetibles con algunos casos límite elaborados con su cerebro y algunos datos aleatorizados que genera solo una vez. Luego, escribe un conjunto de pruebas aleatorias que ejecutes también .

Nunca se debe esperar que las pruebas aleatorias atrapen algo que tus pruebas repetibles fallan. Debería tratar de cubrir todo con pruebas repetibles, y considerar las pruebas aleatorias como una ventaja. Si encuentran algo, debería ser algo que no podrías haber predicho razonablemente; un verdadero bicho raro.

¿Cómo puede su hombre volver a realizar la prueba cuando no ha podido ver si la ha solucionado? Es decir, pierde la repetibilidad de las pruebas.

Si bien creo que probablemente haya algo de 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 más o menos una práctica de “prueba por esperanza”. Creo que, en realidad, tu chico simplemente no piensa en lo que intenta probar, y compensa esa falta de pensamiento al esperar que el azar eventualmente atrape algún misterioso error.

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 de entender lo que intenta probar, probablemente demuestre que no comprende realmente el código que está escribiendo.

    Intereting Posts