Prueba: ¿determinista o no determinista?

16

¿Es mejor tener un

  • Conjunto de pruebas deterministas, que da como resultado que las mismas pruebas tengan éxito
  • Conjunto de pruebas no deterministas, que posiblemente cubre más casos

?

Ejemplo: escribe un conjunto de pruebas para probar la funcionalidad del controlador en una aplicación MVC. El controlador requiere datos de la aplicación de una base de datos como entrada durante la prueba. Hay dos opciones para hacer esto:

  • Usted codifica qué fila (s) de la base de datos de prueba se seleccionan como entrada (por ejemplo, la fila 10 y 412)
  • Utiliza un generador de números aleatorios para elegir pseudoaleatoriamente los datos de la base de datos (dos filas seleccionadas por un generador de números aleatorios)

El primero es determinista: cada ejecución de la prueba para la misma revisión de código debería producir el mismo resultado. El segundo no es determinista: cada ejecución del conjunto de pruebas tiene la posibilidad de producir un resultado diferente. Sin embargo, los datos elegidos al azar podrían ser una mejor representación de los casos límite de datos. ¿Podría simular a un usuario que alimenta mejor a nuestros controladores con datos impredecibles?

¿Cuáles son las razones para elegir uno sobre el otro?

DCKing
fuente
55
Esa prueba simplemente falla, a veces. martinfowler.com/articles/nonDeterminism.html
Gracias por ese enlace. Con ese artículo en mente, sentí que necesitaba aclarar que no determinismo significa en el contexto de este conjunto de pruebas. Como los datos se seleccionan aleatoriamente de una base de datos, todos los datos que se envían al controlador son datos válidos de forma predeterminada. Esto significa que no existen falsos negativos en el conjunto de pruebas cuando se trata del no determinismo. En cierto modo, esta aleatoriedad simula a un usuario seleccionando datos 'al azar' para usar en un controlador. Esto no es necesariamente el mismo no determinismo que discute el artículo, ¿verdad?
DCKing
10
@DCKing: considere lo que sucede si su prueba falla. De acuerdo, tienes un error. Uh, ahora que? ¡Ejecútelo nuevamente en modo de depuración! ¡Donde tiene éxito! Como lo hace las próximas cien veces que lo ejecutas, y luego descartas el problema como un rayo cósmico. La no determinación en las pruebas suena absolutamente inviable. Si siente la necesidad de cubrir más terreno en sus casos de prueba, cubra más terreno. Inicie su RNG con una semilla establecida y ejecute la "prueba" unos cientos de veces con valores aleatorios consistentes.
Phoshi
1
(finalmente llegué a una máquina en la que podía buscar correctamente en Twitter: el " Esa prueba a veces falla " es de #FiveWordTechHorrors en Twitter - quería acreditarlo correctamente)

Respuestas:

30

Cuando cada ejecución de la suite de prueba le da la posibilidad de obtener un resultado diferente, la prueba es casi completamente inútil: cuando la suite muestra un error, tiene una alta probabilidad de que no pueda reproducirlo, y cuando intenta corregir el problema error, no puede verificar si su solución funciona.

Entonces, cuando crea que necesita usar algún tipo de generador de números aleatorios para generar sus datos de prueba, asegúrese de inicializar siempre el generador con la misma semilla, o conserve sus datos de prueba aleatorios en un archivo antes de ingresarlos en su prueba, para que pueda volver a ejecutar la prueba nuevamente con exactamente los mismos datos de la ejecución anterior. De esta manera, puede transformar cualquier prueba no determinista en una prueba determinista.

EDITAR: Usar un generador de números aleatorios para elegir algunos datos de prueba es, en mi humilde opinión, una señal de ser demasiado flojo para elegir buenos datos de prueba. En lugar de arrojar 100,000 valores de prueba elegidos al azar y esperar que esto sea suficiente para descubrir todos los errores serios por casualidad, mejor usa tu cerebro, elige entre 10 y 20 casos "interesantes" y úsalos para el conjunto de pruebas. Esto no solo dará como resultado una mejor calidad de sus pruebas, sino también un rendimiento mucho mayor de la suite.

Doc Brown
fuente
Gracias por tu respuesta. ¿Cuál es su opinión sobre el comentario que hice a mi pregunta?
DCKing
1
@DCKing: si realmente crees que un generador aleatorio será mejor para elegir buenos casos de prueba que tú (lo que dudo), úsalo una vez para encontrar combinaciones de datos de prueba donde falla tu programa, y ​​coloca esas combinaciones en la parte "codificada" de su conjunto de pruebas.
Doc Brown
Gracias de nuevo. Actualicé mi respuesta para que no parezca aplicarse solo a aplicaciones MVC.
DCKing
1
En algunos contextos de IU (por ejemplo, juegos que toman la entrada del controlador) tener programas de prueba que generan una entrada de clave aleatoria puede ser útil para pruebas de estrés. Pueden descubrir defectos que son difíciles de encontrar con una aportación deliberada.
Gort the Robot
@StevenBurnap: bueno, según entiendo la pregunta, creo que el OP tenía en mente las pruebas de regresión más convencionales. Por supuesto, estoy de acuerdo, la prueba de esfuerzo es un caso especial que también puede depender del hardware y dar lugar a un comportamiento no determinista, incluso cuando no se utiliza un generador aleatorio. Eso es algo descrito en el artículo vinculado por MichaelT en el primer comentario debajo de la pregunta. E incluso en las pruebas de estrés con entrada aleatoria, al menos se puede intentar hacer que el comportamiento sea más determinista utilizando una semilla aleatoria definida.
Doc Brown
4

Tanto los deterministas como los no deterministas tienen un lugar

Los dividiría de la siguiente manera:

Pruebas unitarias.

Estos deben tener pruebas deterministas y repetibles con los mismos datos exactos cada vez. Las pruebas unitarias acompañan secciones específicas de código aisladas y deben probarlas de manera determinista.

Pruebas de estrés funcional y de entrada.

Estos pueden usar el enfoque no determinista con las siguientes advertencias:

  • ese hecho está claramente delineado y llamado
  • los valores aleatorios seleccionados se registran y se pueden volver a intentar manualmente
Michael Durrant
fuente
3

Ambos.

Las pruebas deterministas y no deterministas tienen diferentes casos de uso y diferentes valores para su suite. En general, las no deterministas no pueden proporcionar la misma precisión que las pruebas deterministas, que lentamente se han convertido en "las pruebas no deterministas no aportan ningún valor". Esto es falso Pueden ser menos precisos, pero también pueden ser mucho más amplios, lo que tiene sus propios beneficios.

Tomemos un ejemplo: escribe una función que ordena una lista de enteros. ¿Cuáles serían algunas de las pruebas unitarias deterministas que encontrarías útiles?

  • Una lista vacia
  • Una lista con solo un elemento
  • Una lista con todos los mismos elementos.
  • Una lista con múltiples elementos únicos.
  • Una lista con múltiples elementos, algunos de los cuales son duplicados.
  • Una lista con NaN, INT_MINyINT_MAX
  • Una lista que ya está parcialmente ordenada
  • Una lista con 10,000,000 elementos

¡Y eso es solo una función de clasificación! Claro, podría argumentar que algunos de estos son innecesarios, o que algunos de estos pueden descartarse con un razonamiento informal. Pero somos ingenieros y hemos visto explotar el razonamiento informal en nuestra cara. Sabemos que no somos lo suficientemente inteligentes como para comprender completamente los sistemas que hemos construido o mantener la complejidad en nuestras cabezas. Es por eso que escribimos pruebas en primer lugar. Agregar pruebas no deterministas solo dice que no necesariamente debemos ser lo suficientemente inteligentes como para conocer todas las buenas pruebas a priori. Al incluir datos semialeatorios en su función, es mucho más probable que encuentre un caso límite que se haya perdido.

Por supuesto, eso tampoco descarta las pruebas deterministas. Las pruebas no deterministas ayudan a encontrar errores en grandes extensiones del programa. Sin embargo, una vez que haya encontrado los errores, necesita una forma reproducible de mostrar que lo solucionó. Entonces:

  • Use pruebas no deterministas para encontrar errores en su código.
  • Use pruebas deterministas para verificar las correcciones en su código.

Tenga en cuenta que esto significa que muchos consejos sólidos sobre las pruebas unitarias no se aplican necesariamente a las pruebas no deterministas. Por ejemplo, que deben ser rápidos. Las pruebas de propiedad de bajo nivel deben ser rápidas, pero una prueba no determinista como "simular un usuario haciendo clic al azar en los botones de su sitio web y asegurarse de que nunca obtenga un error 500" debería favorecer la exhaustividad sobre la velocidad. Simplemente haga que una prueba como esa se ejecute independientemente de su proceso de compilación para que no ralentice el desarrollo. Por ejemplo, ejecútelo en su propio cuadro de ensayo privado.

Hovercouch
fuente
-1

Realmente no quieres determinista versus no determinista.

Lo que puede desear es "siempre lo mismo" frente a "no siempre lo mismo".

Por ejemplo, puede tener un número de compilación que aumenta con cada compilación, y cuando desea algunos números aleatorios, inicializa un generador de números aleatorios con el número de compilación como semilla. Entonces, en cada compilación, haces tus pruebas con diferentes valores, dándote más oportunidades de encontrar errores.

Pero una vez que se encuentra un error, todo lo que necesita hacer es ejecutar la prueba con el mismo número de compilación, y es reproducible.

gnasher729
fuente
1
O si no tiene un número de compilación para usar, coloque el valor inicial de la semilla en la salida de la ejecución de prueba, para que pueda volver a ejecutar las pruebas con la misma semilla.
RemcoGerlich