¿Están escribiendo manualmente pruebas unitarias de prueba por ejemplo?

9

Sabemos que escribir pruebas JUnit demuestra una ruta particular a través de su código.

Uno de mis asociados comentó:

La escritura manual de pruebas unitarias es prueba por ejemplo .

Provenía de Haskell, que tiene herramientas como Quickcheck y la capacidad de razonar sobre el comportamiento del programa con los tipos .

Su implicación fue que hay muchas otras combinaciones de entradas que no se prueban con este método para las cuales su código no se prueba.

Mi pregunta es: ¿están escribiendo manualmente pruebas unitarias de prueba por ejemplo?

ojo de halcón
fuente
3
No, no escribir / usar pruebas. Afirmar que las pruebas de su unidad son prueba de que no hay nada de malo en el programa es Prueba por ejemplo (una generalización inapropiada). Las pruebas no se tratan de probar matemáticamente la corrección del código: las pruebas son, por su naturaleza, verificaciones experimentales. Es una red de seguridad que lo ayuda a generar confianza al decirle algo sobre el código. Pero usted es quien tiene que elegir una buena estrategia para sondear el código, y usted es quien tiene que interpretar qué significan esos datos.
Filip Milovanović

Respuestas:

10

Si elige aleatoriamente entradas para la prueba, entonces supongo que es posible que esté ejerciendo una falacia lógica de Prueba por ejemplo.

Pero las buenas pruebas unitarias nunca hacen eso. En cambio, tratan en rangos y casos extremos.

Por ejemplo, si escribiera pruebas unitarias para una función de valor absoluto que acepta un entero como entrada, no necesitaría probar todos los valores posibles de entrada para demostrar que el código funciona. Para obtener una prueba exhaustiva, necesitaría solo cinco valores: -1, 0, 1 y los valores máximo y mínimo para el entero de entrada.

Estos cinco valores prueban cada rango posible y caso límite de la función. No necesita probar cualquier otro valor de entrada posible (es decir, cada número que el tipo entero puede representar) para obtener un alto nivel de confianza de que la función funciona para todos los valores de entrada.

Robert Harvey
fuente
11
Un probador de código ingresa a un bar y ordena una cerveza. 5 cervezas -1 cervezas, MAX_VALUE cervezas, un pollo. un nulo
Neil
2
Los "5 valores" son pura tontería. Considere una función trivial como int foo(int x) { return 1234/(x - 100); }. También tenga en cuenta que (dependiendo de lo que esté probando) es posible que deba asegurarse de que la entrada no válida ("fuera de rango") devuelva resultados correctos (por ejemplo, `` find_thing (thing) `devuelve correctamente algún tipo de estado" no encontrado " si la cosa no fue encontrada).
Brendan
3
@Brendan: No hay nada significativo en que sean cinco valores; Resulta que son cinco valores en mi ejemplo. Su ejemplo tiene un número diferente de pruebas porque está probando una función diferente. No digo que cada función requiera exactamente cinco pruebas; dedujiste eso al leer mi respuesta.
Robert Harvey
1
Las bibliotecas de pruebas generativas suelen ser mejores para probar casos extremos que usted. Si, por ejemplo, que estaba usando flotadores en lugar de números enteros, la biblioteca también se comprobaría -Inf, Inf, NaN, 1e-100, -1e-100, -0, 2e200... prefiero no tengo que hacer todos los manualmente.
Hovercouch
@Hovercouch: Si conoces uno bueno, me encantaría saberlo. El mejor que he visto fue Pex; sin embargo, fue increíblemente inestable. Recuerde, estamos hablando de funciones relativamente simples aquí. Las cosas se ponen más difíciles cuando se trata de cosas como la lógica empresarial de la vida real.
Robert Harvey
8

Cualquier prueba de software es como "Prueba por ejemplo", no solo pruebas unitarias utilizando una herramienta como JUnit. Y eso no es nueva sabiduría, hay una cita de Dijkstra de 1960, que dice esencialmente lo mismo:

"Las pruebas muestran la presencia, no la ausencia de errores"

(solo reemplace las palabras "shows" por "pruebas"). Sin embargo, esto también es cierto para las herramientas que generan datos de prueba aleatorios. El número de posibles entradas para una función del mundo real suele ser mayor en órdenes de magnitud que el número de casos de prueba que uno puede producir y verificar contra un resultado esperado dentro de la edad del universo, independientemente del método de generación de esos casos, por lo que incluso si uno usa una herramienta generadora para producir muchos datos de prueba, no hay garantía de no perderse el único caso de prueba que podría haber detectado un determinado error.

Las pruebas aleatorias a veces pueden revelar un error que fue ignorado por los casos de prueba creados manualmente. Pero, en general, es más eficiente diseñar cuidadosamente las pruebas para la función que se va a probar, y asegurarse de que uno obtenga un código completo y una cobertura de ramificación con el menor número de casos de prueba posible. A veces es una estrategia factible combinar pruebas generadas manualmente y al azar. Además, cuando se usan pruebas aleatorias, uno debe tener cuidado para obtener los resultados de manera reproducible.

Por lo tanto, las pruebas creadas manualmente no son peores que las pruebas generadas al azar, a menudo todo lo contrario.

Doc Brown
fuente
1
Cualquier conjunto de pruebas prácticas que use una verificación aleatoria también tendrá pruebas unitarias. (Técnicamente, las pruebas unitarias son solo un caso degenerado de pruebas aleatorias). Su redacción sugiere que las pruebas aleatorias son difíciles de lograr, o que la combinación de pruebas aleatorias y pruebas unitarias es difícil. Este no suele ser el caso. En mi opinión, uno de los mayores beneficios de las pruebas aleatorias es que alienta a escribir las pruebas como propiedades sobre el código que siempre se tienen. Prefiero tener estas propiedades explícitamente establecidas (¡y marcadas!) Que tener que inferirles algunas pruebas puntuales.
Derek Elkins dejó SE
@DerekElkins: "difícil" es en mi humilde opinión el término equivocado. Las pruebas aleatorias requieren bastante esfuerzo, y ese es un esfuerzo que reduce el tiempo disponible para las pruebas de artesanía (y si tienes personas que simplemente siguen consignas como la mencionada en la pregunta, probablemente no harán ninguna artesanía). Solo arrojar una gran cantidad de datos de prueba aleatorios en un fragmento de código es solo la mitad del trabajo, uno también tiene que producir los resultados esperados para cada una de esas entradas de prueba. Para algunos escenarios, esto se puede hacer automáticamente. Para otros, no.
Doc Brown
Si bien definitivamente hay momentos en los que es necesario pensar un poco para elegir una buena distribución, esto generalmente no es un problema importante. Su comentario sugiere que está pensando en esto de manera incorrecta. Las propiedades que escribe para la verificación aleatoria son las mismas propiedades que escribiría para la verificación del modelo o para las pruebas formales. De hecho, pueden ser y han sido utilizados para todas esas cosas al mismo tiempo. No hay "resultados esperados" que deba producir también. En cambio, simplemente declara una propiedad que siempre debe contener. Algunos ejemplos: 1) empujar algo en una pila y ...
Derek Elkins dejó SE
... entonces estallar debería ser lo mismo que no hacer nada; 2) cualquier cliente con un saldo superior a $ 10,000 debe obtener la tasa de interés de saldo alto y solo entonces; 3) la posición del sprite siempre está dentro del cuadro delimitador de la pantalla. Algunas propiedades pueden corresponder a pruebas puntuales, por ejemplo, "cuando el saldo es $ 0, da la advertencia de saldo cero". Las propiedades son especificaciones parciales con el ideal de obtener una especificación total. Tener dificultades para pensar en estas propiedades significa que no tiene claro cuál es la especificación y, a menudo, significa que tendrá dificultades para pensar en buenas pruebas unitarias.
Derek Elkins salió del SE
0

Escribir pruebas manualmente es "prueba con el ejemplo". Pero también lo es QuickCheck y, en cierta medida, los sistemas de tipos. Cualquier cosa que no sea una verificación formal directa estará limitada en lo que le dice sobre su código. En cambio, debe pensar en términos del mérito relativo de los enfoques.

Las pruebas generativas, como QuickCheck, son realmente buenas para barrer un amplio espacio de entradas. También es mucho mejor para abordar casos extremos que las pruebas manuales: las bibliotecas de pruebas generativas tendrán más experiencia con esto que usted. Por otro lado, solo le informan sobre invariantes, no resultados específicos. Entonces, para validar que su programa está obteniendo los resultados correctos, aún necesita algunas pruebas manuales para verificar que, de hecho foo(bar) = baz,.

Hovercouch
fuente