Me preguntaron sobre cómo ejecutar un conjunto de 65.000.000.000 de pruebas y me pregunto si es normal tener un proyecto con una cantidad tan grande de pruebas.
¿Has trabajado en proyectos con esta característica?
Me preguntaron sobre cómo ejecutar un conjunto de 65.000.000.000 de pruebas y me pregunto si es normal tener un proyecto con una cantidad tan grande de pruebas.
¿Has trabajado en proyectos con esta característica?
Con 65 mil millones de pruebas, parece que te piden que pruebes todas las entradas posibles. Esto no es útil: básicamente estaría probando que su procesador funciona correctamente, no que su código sea correcto.
Debería estar probando clases de equivalencia . Esto reducirá drásticamente su rango de entradas de prueba.
También considere si puede subdividir su sistema en partes más pequeñas. Cada pieza será más fácil de probar de forma aislada, y luego puede realizar algunas pruebas de integración que reúnen todas las piezas.
Si aún desea que la seguridad de que algunas de esas combinaciones de entrada funcionen, quizás podría intentar fuzz testing . Obtendrá algunos de los beneficios de probar un montón de entradas diferentes, pero sin ejecutar todos los 65 mil millones de ellos.
Si se trata de un conjunto de pruebas real, entonces no querrás estar cerca de trabajar en ello.
El trabajo completo de un probador es encontrar un equilibrio entre las pruebas lo suficientemente exhaustivas como para estar seguro de que obtienes los resultados "correctos" y escribir pocas pruebas suficientes para que se puedan ejecutar en un tiempo razonable.
Muchas pruebas se pueden resumir en "clases de equivalencia", lo que significa que en lugar de ejecutar 3 mil millones de pruebas, ejecuta 1 que le da un nivel razonable de confianza de que todas las demás pruebas en esa clase de equivalencia se ejecutarán con éxito, si decide malgastar el tiempo ejecutándolos.
Debería decirle a quien esté pensando en realizar 65 mil millones de pruebas que necesitan hacer un mejor trabajo resumiendo las pruebas en clases de equivalencia.
Más que probable, llegó a su cifra de 65 mil millones de pruebas calculando todas las combinaciones posibles de entradas en el sistema bajo prueba, o calculando la complejidad ciclomática y asumiendo que se debe escribir una prueba para cada una de estas rutas de ejecución únicas.
Esta no es la forma en que se escriben las pruebas reales, porque, como lo han indicado otros carteles y comentaristas, el poder técnico requerido para ejecutar 65 pruebas mil millones es asombroso. Esto sería como escribir una prueba que ejerce un método para agregar dos enteros al insertar cada permutación posible de dos valores de 32 bits y verificar el resultado. Es una locura total. Debe trazar la línea e identificar un subconjunto de todos los casos de prueba posibles, lo que, entre ellos, garantizaría que el sistema se comporte como se espera en todo el rango de entradas. Por ejemplo. prueba agregando algunos números "ordinarios", prueba algunos escenarios de números negativos, prueba límites técnicos como escenarios de desbordamiento, y prueba cualquier escenario que pueda dar como resultado un error. Como se mencionó, estos diversos tipos de pruebas ejercitan "clases de equivalencia"; le permiten tomar una muestra representativa de las posibles entradas, junto con los "valores atípicos" conocidos, y decir con una confianza extremadamente alta que, dado que estos escenarios pasan, todos los escenarios similares a estos pasarán.
Considere uno de los katas de código básicos, el generador de números romanos. La tarea, que se realizará utilizando técnicas TDD en un estilo "dojo", es escribir una función que pueda aceptar cualquier número del 1 al 3000 y producir el número romano correcto para ese valor numérico.
No resuelves este problema escribiendo pruebas de 3000 unidades, una a la vez, y pasándolas por turno. Eso es una locura; el ejercicio normalmente toma entre una y dos horas, y usted estará allí durante días probando cada valor individual. En su lugar, te vuelves inteligente. Comience con el caso base más simple (1 == "I"), implemente eso usando una estrategia de "código mínimo" ( return "I";
), y luego busque cómo se comportará incorrectamente el código que tiene en otro escenario esperado (2 == "II"). Enjuague y repita; más que probable, reemplazó su implementación inicial con algo que repite el carácter "I" tantas veces como sea necesario (como return new String('I',number);
). Eso obviamente pasará una prueba para III, así que no te molestes; en su lugar, escribe la prueba para 4 == "IV", que sabe que la implementación actual no funcionará correctamente.
O, en un estilo más analítico, examina cada decisión condicional que toma el código (o debe ser) y escribe una prueba diseñada para ingresar el código para cada resultado posible de cada decisión. Si tiene 5 sentencias if (cada una con una rama verdadera y falsa), cada una de ellas totalmente independiente de la otra, debe codificar 10 pruebas, no 32. Cada prueba se diseñará para afirmar dos cosas sobre una posible decisión particular; primero que se tome la decisión correcta, y luego que el código ingresado dado que la condición es correcta. Usted no codifica una prueba para cada posible permutación de decisiones independientes. Si las decisiones son dependientes, entonces tienes que probar más de ellas en combinación, pero hay menos combinaciones de este tipo porque algunas decisiones solo se toman cuando otra decisión tuvo un resultado particular.
¿Es esto "normal" ?, no. Donde "normal" se define como el promedio o la experiencia típica. No puedo decir que alguna vez tuve que trabajar en un proyecto como ese, pero he estado en un proyecto en el que uno de cada pocos millones de bits se voltearía. Probar eso fue ... un desafío.
¿Es potencialmente necesario? Bueno, eso depende de las garantías y especificidades del proyecto. Es un poco incrédulo comprender al principio, pero tu pregunta es ligera en detalles específicos.
Como han señalado otros (MichaelT), el tiempo para completar esta tarea con la prueba en serie hace que esto no sea práctico. Entonces la paralelización se convierte en su primera consideración. ¿Cuántos sistemas de prueba puede hacer frente a este problema y qué apoyo tiene para recopilar los resultados de esos sistemas múltiples?
¿Qué garantías tiene de que el dispositivo o el algoritmo que está probando se está replicando de manera confiable? El software es bastante confiable en la replicación, pero los dispositivos de hardware (especialmente la primera generación) pueden tener problemas de fabricación. Un error falso en la prueba en ese caso podría indicar un mal algoritmo o si el dispositivo no se ensambla correctamente. ¿Necesita distinguir entre estos dos casos?
También deberá considerar cómo va a validar los sistemas de prueba. Suponiendo una razón legítima para tantos casos de prueba, va a necesitar mucha automatización. Esa automatización debe ser inspeccionada para asegurarse de que no se equivoca al generar sus casos de prueba. Las verificaciones en busca de errores realmente serían el equivalente a encontrar una aguja en el pajar.
Este arstechnica link puede o no puede arrojar alguna información sobre sus consideraciones de prueba. Los clústeres de GPU se usan comúnmente para contraseñas de craqueo de fuerza bruta. El que se cita en el artículo puede can cycle through as many as 350 billion guesses per second
, por lo que ese tipo pone sus pruebas de 65B en perspectiva. Es probable que sea un dominio diferente, pero muestra cómo abordar la tarea desde diferentes ángulos puede proporcionar una solución viable.
No creo que sea factible mantener 6.5e + 10 lo prueba en primer lugar, por lo que ejecutarlos puede ser discutible. Incluso los proyectos más grandes, como Debian con todos sus paquetes, tienen solo varios cientos de millones de SLOCs.
Pero si tienes que realizar una gran cantidad de pruebas de todos modos, hay algunas estrategias.
No los ejecutes todos. Probablemente no todas las pruebas dependen de cada ruta de código. Defina las dependencias entre los subsistemas y sus pruebas, y entre las suites de prueba, y solo podrá ejecutar pruebas unitarias relevantes para un cambio en particular, solo las pruebas de integración que dependen de estas pruebas unitarias, etc.
Ejecútalos en paralelo. Con el código base tan grande, es probable que tenga una granja de servidores de compilación masiva (de vuelta en JetBrains, una operación relativamente pequeña, solíamos tener entre 40 y 50 agentes de compilación que se ejecutaban solo en la granja de servidores de compilación e integración continua de IDEA). Como las pruebas unitarias son independientes y las pruebas de integración pueden reutilizar el código ya creado, las pruebas son relativamente fáciles de paralelizar.
Deja de correr temprano. Si sabe que un conjunto de pruebas en particular depende de su funcionamiento razonable en la corrección de otro conjunto de pruebas, puede cortar toda la cadena una vez que vea que falla un enlace.
Descargo de responsabilidad: no soy un ingeniero de pruebas profesional. Tome lo anterior con un grano de sal.
Aunque ha habido varias buenas sugerencias aquí sobre cómo intentar pasar sigilosamente con menos pruebas, dudo seriamente que su sistema tenga solo 65,000 millones de combinaciones de entrada. Eso es menos de 36 bits de entrada. Supongamos que ya ha tomado todos los consejos dados anteriormente.
Si cada prueba tarda aproximadamente un milisegundo en ejecutarse y usted distribuye las pruebas en solo 10 procesadores (una PC normal), la prueba se ejecutará en poco más de 69 días. Eso es un tiempo, pero no del todo irrazonable. Distribuya entre 100 procesadores (una docena de PC normales o una PC de servidor razonable) y las pruebas se completarán en menos de 7 días. Puede ejecutarlos todas las semanas para verificar si hay regresiones.
Lea otras preguntas en las etiquetas unit-testing testing continuous-integration