Al crear un sistema como una IA, que puede tomar muchas rutas diferentes muy rápidamente, o realmente cualquier algoritmo que tenga varias entradas diferentes, el posible conjunto de resultados puede contener una gran cantidad de permutaciones.
¿Qué enfoque se debe tomar para usar TDD al crear un sistema que genera muchas, muchas permutaciones diferentes de resultados?
Respuestas:
Tomando un enfoque más práctico para la respuesta de pdr . TDD tiene que ver con el diseño de software más que con las pruebas. Utiliza pruebas unitarias para verificar su trabajo a medida que avanza.
Entonces, en un nivel de prueba de unidad, debe diseñar las unidades para que puedan probarse de una manera completamente determinista. Puede hacer esto tomando cualquier cosa que haga que la unidad no sea determinista (como un generador de números aleatorios) y abstraiga eso. Digamos que tenemos un ejemplo ingenuo de un método para decidir si un movimiento es bueno o no:
Este método es muy difícil de probar y lo único que realmente puede verificar en las pruebas unitarias son sus límites ... pero eso requiere muchos intentos para llegar a los límites. Por lo tanto, abstraigamos la parte aleatoria creando una interfaz y una clase concreta que envuelva la funcionalidad:
La
Decider
clase ahora necesita usar la clase concreta a través de su abstracción, es decir, la interfaz. Esta forma de hacer las cosas se llama inyección de dependencia (el ejemplo a continuación es un ejemplo de inyección de constructor, pero también puede hacerlo con un setter):Puede preguntarse por qué es necesaria esta "acumulación de código". Bueno, para empezar, ahora puede burlarse del comportamiento de la parte aleatoria del algoritmo porque
Decider
ahora tiene una dependencia que sigue elIRandom
"contrato" de s. Puede usar un marco de imitación para esto, pero este ejemplo es lo suficientemente simple como para codificarse:La mejor parte es que esto puede reemplazar completamente la implementación concreta "real". El código se vuelve fácil de probar así:
Espero que esto le brinde ideas sobre cómo diseñar su aplicación para que las permutaciones se puedan forzar para que pueda probar todos los casos límite y demás.
fuente
El TDD estricto tiende a descomponerse un poco para sistemas más complejos, pero eso no importa demasiado en términos prácticos: una vez que vaya más allá de poder aislar entradas individuales, simplemente elija algunos casos de prueba que brinden una cobertura razonable y utilícelos.
Esto requiere cierto conocimiento de lo que la implementación va a hacer bien, pero eso es más una preocupación teórica: es muy poco probable que esté construyendo una IA que fue especificada en detalle por usuarios no técnicos. Está en la misma categoría que pasar las pruebas al codificar los casos de prueba: oficialmente, la prueba es la especificación y la implementación es correcta y la solución más rápida posible, pero en realidad nunca sucede.
fuente
TDD no se trata de pruebas, se trata de diseño.
Lejos de desmoronarse con la complejidad, sobresale en estas circunstancias. Lo llevará a considerar el problema más grande en piezas más pequeñas, lo que conducirá a un mejor diseño.
No intente probar cada permutación de su algoritmo. Simplemente cree prueba tras prueba, escriba el código más simple para que la prueba funcione, hasta que tenga cubiertas sus bases. Debería ver a qué me refiero con analizar el problema porque se lo alentará a simular partes del problema mientras prueba otras partes, para evitar tener que escribir 10 mil millones de pruebas por 10 mil millones de permutaciones.
Editar: quería agregar un ejemplo, pero no tuve tiempo antes.
Consideremos un algoritmo de clasificación in situ. Podríamos seguir adelante y escribir pruebas que cubran el extremo superior de la matriz, el extremo inferior de la matriz y todo tipo de combinaciones extrañas en el medio. Para cada uno, tendríamos que construir una matriz completa de algún tipo de objeto. Esto llevaría tiempo.
O podríamos abordar el problema en cuatro partes:
La primera es la única parte complicada del problema, pero al abstraerlo del resto, lo ha simplificado mucho.
El segundo es casi seguro manejado por el objeto mismo, al menos opcionalmente, en muchos marcos de tipo estático habrá una interfaz para mostrar si esa funcionalidad está implementada. Entonces no necesitas probar esto.
El tercero es increíblemente fácil de probar.
El cuarto solo maneja dos punteros, le pide a la clase transversal que mueva los punteros, solicita una comparación y, en función del resultado de esa comparación, pide que se intercambien los elementos. Si has fingido los primeros tres problemas, puedes probar esto muy fácilmente.
¿Cómo hemos llevado a un mejor diseño aquí? Digamos que lo has mantenido simple e implementado un tipo de burbuja. Funciona pero, cuando vas a producción y tiene que manejar un millón de objetos, es demasiado lento. Todo lo que tiene que hacer es escribir una nueva funcionalidad transversal e intercambiarla. No tiene que lidiar con la complejidad de manejar los otros tres problemas.
Esto, encontrará, es la diferencia entre las pruebas unitarias y TDD. El probador de unidades dirá que esto ha hecho que sus pruebas sean frágiles, que si hubiera probado entradas y salidas simples, ahora no tendría que escribir más pruebas para su nueva funcionalidad. El TDDer dirá que he separado las preocupaciones adecuadamente para que cada clase que tengo haga una cosa y una cosa bien.
fuente
No es posible probar cada permutación de un cálculo con muchas variables. Pero eso no es nada nuevo, siempre ha sido cierto para cualquier programa por encima de la complejidad del juguete. El objetivo de las pruebas es verificar la propiedad del cálculo. Por ejemplo, ordenar una lista con 1000 números requiere un poco de esfuerzo, pero cualquier solución individual se puede verificar muy fácilmente. Ahora, aunque hay 1000! posibles (clases de) entradas para ese programa y no puede probarlas todas, es completamente suficiente generar solo 1000 entradas al azar y verificar que la salida está, de hecho, ordenada. ¿Por qué? Debido a que es casi imposible escribir un programa que clasifique de manera confiable 1000 vectores generados aleatoriamente sin ser también correctos en general (a menos que lo manipules deliberadamente para manipular ciertas entradas mágicas ...)
Ahora, en general, las cosas son un poco más complicadas. Realmente ha habido errores en los que un remitente no enviaba correos electrónicos a los usuarios si tenían una 'f' en su nombre de usuario y el día de la semana es el viernes. Pero considero que es un esfuerzo desperdiciado tratar de anticipar tal rareza. Su conjunto de pruebas debe proporcionarle una confianza constante de que el sistema hace lo que espera en las entradas que espera. Si hace cosas funky en ciertos casos funky, lo notará lo suficientemente pronto después de probar el primer caso funky, y luego puede escribir una prueba específicamente para ese caso (que generalmente también cubrirá una clase completa de casos similares).
fuente
Tome los casos extremos más alguna entrada aleatoria.
Para tomar el ejemplo de clasificación:
Si funciona rápido para estos, puede estar seguro de que funcionará para todas las entradas.
fuente