Pregunta

Como programador, he comprado de todo corazón en la filosofía TDD y tomar el esfuerzo para hacer extensas pruebas unitarias para cualquier código no trivial que escribo. A veces, este camino puede ser doloroso (cambios de comportamiento que causan en cascada cambios de prueba de unidad múltiple; altas cantidades de andamiaje necesario), pero en general me niegan a programar y sin pruebas que puedo correr después de cada cambio, y mi código es mucho menos bugs como resultado.

Recientemente, he estado jugando con Haskell, y es residente de testing, QuickCheck. De una manera claramente diferente de TDD, QuickCheck tiene un énfasis en invariantes de prueba del código, es decir, ciertas propiedades que sujetan sobre todas (o de fondo) subconjuntos de los insumos. Un ejemplo rápido: un algoritmo de ordenación estable debería dar la misma respuesta si corremos dos veces, debe tener salida en aumento, debe ser una permutación de la entrada, etc. A continuación, QuickCheck genera una variedad de datos aleatorios con el fin de probar estas invariantes.

A mí me parece, por lo menos para las funciones puras (es decir, las funciones sin efectos secundarios - y si burlarse correctamente puede convertir funciones sucia en los puros), que las pruebas invariante podría suplantar a las pruebas unitarias como un superconjunto estricto esas capacidades. Cada unidad de prueba consiste en una entrada y una salida (en lenguajes de programación imperativo, la "salida" no es sólo el retorno de la función, sino también cualquier cambio de estado, pero esto puede ser encapsulado). Uno podría concebiblemente creado un generador aleatorio de entrada que es lo suficientemente bueno como para cubrir todas las entradas de prueba unidad que habría creado manualmente (y algo más, porque habría que generaría casos que no habría pensado); si se encuentra un error en su programa debido a alguna condición de frontera, a mejorar su generador aleatorio de entrada de modo que genere ese caso también.

El desafío, entonces, es si es o no es posible formular invariantes útiles para todos los problemas. Yo diría que es: es mucho más fácil una vez que tenga una respuesta para ver si es correcta de lo que es para calcular la respuesta en el primer lugar. Pensando en invariantes también contribuye a aclarar la especificación de un complejo algoritmo mucho mejor que los casos de prueba ad hoc, que fomentan una especie de pensamiento caso por caso del problema. Se podría utilizar una versión anterior de su programa como un modelo de aplicación, o una versión de un programa en otro idioma. Etc. Eventualmente, se podría cubrir la totalidad de sus antiguos casos de prueba sin tener que codificar de forma explícita una entrada o una salida.

¿Me he vuelto loco, o estoy en lo cierto?

¿Fue útil?

Solución

Un año más tarde, ahora que tengo una respuesta a esta pregunta: No En particular, las pruebas unitarias serán siempre necesarios y útiles para pruebas de regresión, en el que una prueba se une a una! informe de error y sigue vivo en el código base para evitar que ese error se vuelva siempre.

Sin embargo, sospecho que cualquier prueba de la unidad puede ser reemplazada con una prueba de cuyas entradas están generado aleatoriamente. Incluso en el caso del código imperativo, la “entrada” es el orden de las sentencias imperativas que necesita hacer. Por supuesto, si es o no la pena crear el generador de datos aleatorios, y si o no puede hacer que el generador de datos aleatorios tiene el derecho de distribución es otra cuestión. Prueba de la unidad es simplemente un caso degenerado en el que el generador aleatorio siempre da el mismo resultado.

Otros consejos

Lo que has criado es un muy buen punto - cuando solamente se aplica a la programación funcional. Usted ha declarado un medio de conseguir esto todos con código imperativo, sino que también se refirió a por qué no se hace - no es particularmente fácil

.

Creo que esa es la razón de que no va a sustituir la unidad de pruebas: no se ajusta para el código imperativo tan fácilmente

.

dudoso

Sólo he oído hablar de (no se utiliza) este tipo de pruebas, pero veo dos problemas potenciales. Me gustaría tener comentarios acerca de cada uno.

resultados engañosos

He oído hablar de pruebas como:

  • reverse(reverse(list)) debe ser igual list
  • unzip(zip(data)) debe ser igual data

Sería muy bueno saber que éstos son válidas para una amplia gama de entradas. pero tanto estas pruebas pasarían si las funciones simplemente devuelven su entrada.

Me parece que te gustaría para verificar que, por ejemplo, es igual a reverse([1 2 3]) [3 2 1] para probar el comportamiento correcto en al menos un caso, a continuación, Añadir algunas pruebas con datos aleatorios.

complejidad de prueba

Una prueba invariante que describe completamente la relación entre la entrada y la salida puede ser más complejo que la propia función. Si es complejo, podría ser con errores, pero que no tiene pruebas para sus pruebas.

Una buena prueba de la unidad, por el contrario, es demasiado simple para atornillar hacia arriba o malinterpretar como un lector. Sólo un error tipográfico podría crear un bug en "esperar reverse([1 2 3]) al igual [3 2 1]".

Lo que usted escribió en su puesto original, me recordó a este problema, que es una pregunta abierta sobre lo que el invariante de bucle es probar el bucle correcta ...

de todos modos, no estoy seguro de lo mucho que han leído en especificación formal, sino que se dirige hacia abajo de esa línea de pensamiento. El libro de David Gries es uno de los clásicos sobre el tema, todavía no domino el concepto lo suficientemente bien como para usarlo rápidamente en mi día a día de programación. la respuesta habitual de especificación formal es, es difícil y complicado, y sólo vale la pena el esfuerzo si se está trabajando en sistemas críticos de seguridad. pero yo creo que son parte de atrás de las técnicas de envoltura similar a lo que QuickCheck expone que se puede utilizar.

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