Estoy escribiendo un código (recursivo) que navega por un gráfico de dependencia para buscar ciclos o contradicciones en las dependencias. Sin embargo, no estoy seguro de cómo abordar la unidad para probar esto. El problema es que una de nuestras principales preocupaciones es que el código maneje todas las estructuras gráficas interesantes que puedan surgir y se asegure de que todos los nodos se manejen de manera adecuada.
Si bien la cobertura del 100% de línea o rama es suficiente para estar seguro de que algunos códigos funcionan, parece que incluso con una cobertura de ruta del 100% aún tendría dudas.
Entonces, ¿cómo se hace para seleccionar las estructuras de gráficos para casos de prueba para tener la confianza de que su código podría manejar todas las permutaciones concebibles que encontrará en los datos del mundo real.
PD: si es importante, todos los bordes en mi gráfico están etiquetados como "debe tener" xo "no puede tener" y no hay ciclos triviales, y solo hay un borde entre dos nodos.
PPS: esta declaración adicional del problema fue publicada originalmente por el autor de la pregunta en un comentario a continuación:
For all vertices N in forest F, for all vertices M, in F, such that if there are any walks between N and M they all must either use only edges labelled 'conflict' or 'requires'.
fuente
Respuestas:
Eso suena como un buen comienzo. Supongo que ya trató de aplicar algunas técnicas clásicas como el análisis del valor límite o la partición de equivalencia , como ya mencionó las pruebas basadas en la cobertura. Después de haber invertido mucho tiempo en la construcción de buenos casos de prueba, llegará a un punto en el que usted, su equipo y también sus evaluadores (si los tiene) se queden sin ideas. Y ese es el punto donde debe dejar el camino de las pruebas unitarias y comenzar a probar con la mayor cantidad de datos del mundo real que pueda.
Debería ser obvio que debe intentar elegir una gran diversidad de gráficos de sus datos de producción. Quizás tenga que escribir algunas herramientas o programas adicionales solo para esa parte del proceso. La parte difícil aquí es probablemente verificar la exactitud de la salida de sus programas, cuando coloca diez mil gráficos diferentes del mundo real en su programa, ¿cómo sabrá si su programa produce siempre la salida correcta? Obviamente no puede verificar que manualmente. Entonces, si tiene suerte, puede realizar una segunda implementación muy simple de su verificación de dependencia, que puede no cumplir con sus expectativas de rendimiento, pero es más fácil de verificar que su algoritmo original. También debe intentar integrar muchas verificaciones de plausibilidad directamente en su programa (por ejemplo,
Finalmente, aprenda a aceptar que cada prueba solo puede probar la existencia de errores, pero no la ausencia de errores.
fuente
1. Generación de prueba aleatoria
Escriba un algoritmo que genere gráficos, haga que genere unos cientos (o más) gráficos aleatorios y arroje cada uno a su algoritmo.
Mantenga la semilla aleatoria de los gráficos que causan fallas interesantes y agréguelos como pruebas unitarias.
2. Codificar partes difíciles
Algunas estructuras de gráficos que usted conoce son difíciles de codificar de inmediato, o puede escribir algún código que las combine y las empuje a su algoritmo.
3. Generar lista exhaustiva
Pero, si quiere estar seguro de que "el código podría manejar todas las permutaciones concebibles que encontrará en los datos del mundo real", debe generar estos datos no a partir de una semilla aleatoria, sino caminando a través de todas las permutaciones. (Esto se hace cuando se prueban los sistemas de señalización del tren subterráneo, y le brinda una enorme cantidad de casos que demoran años en probarse. Para los trenes subterráneos, el sistema está acotado, por lo que hay un límite superior para la cantidad de permutaciones. No estoy seguro de cómo es su caso aplica)
fuente
For all vertices N in forest F, for all vertices M, in F, such that if there are any walks between N and M they all must either use only edges labelled 'conflict' or 'requires'.
el dominio no es el problema.Ninguna prueba en absoluto será suficiente en este caso, ni siquiera toneladas de datos reales o borrosos. Una cobertura de código del 100%, o incluso una cobertura de ruta del 100%, es insuficiente para probar funciones recursivas.
O la función recursiva resiste una prueba formal (no debería ser tan difícil en este caso), o no lo hace. Si el código está demasiado entrelazado con el código específico de la aplicación para descartar efectos secundarios, ahí es donde comenzar.
El algoritmo en sí mismo suena como un algoritmo de inundación simple, similar a una primera búsqueda amplia simple, con la adición de una lista negra que no debe cruzarse con la lista de nodos visitados, que se ejecuta desde todos los nodos.
Este algoritmo iterativo cumple la condición de que no se requiera dependencia y se coloque en la lista negra al mismo tiempo, para gráficos de estructura arbitraria, comenzando desde cualquier artefacto arbitrario por el cual el artefacto de inicio siempre se requiera.
Si bien puede o no ser tan rápido como su propia implementación, se puede demostrar que termina en todos los casos (en cuanto a cada iteración del bucle externo, cada elemento solo se puede empujar una vez en la
tovisit
cola), inunda todo el alcance gráfico (prueba inductiva), y detecta todos los casos en los que se requiere que un artefacto se requiera y se ponga en una lista negra simultáneamente, comenzando desde cada nodo.Si puede demostrar que su propia implementación tiene las mismas características, puede probar la corrección sin resultar en pruebas unitarias. Solo los métodos básicos para empujar y saltar de las colas, contar la longitud de la cola, iterar sobre propiedades y similares deben probarse y demostrarse que están libres de efectos secundarios.
EDITAR: Lo que este algoritmo no prueba es que su gráfico no tenga ciclos. Sin embargo, los gráficos acíclicos dirigidos son un tema bien investigado, por lo que encontrar un algoritmo listo para probar esta propiedad también debería ser fácil.
Como puede ver, no hay necesidad de reinventar la rueda, en absoluto.
fuente
Está utilizando frases como "todas las estructuras gráficas interesantes" y "manejado adecuadamente". A menos que tenga formas de probar su código contra todas esas estructuras y determinar si el código maneja el gráfico adecuadamente, solo puede usar herramientas como el análisis de cobertura de prueba.
Le sugiero que comience buscando y probando con una serie de estructuras gráficas interesantes y determine cuál sería el manejo apropiado y vea que el código lo haga. Luego, puede comenzar a perturbar esas gráficas en a) gráficas rotas que violan las reglas ob) gráficas no tan interesantes que tienen problemas; vea si su código no puede manejarlos correctamente.
fuente
Puede intentar hacer una clasificación topológica y ver si tiene éxito. Si no es así, entonces tiene al menos un ciclo.
fuente
Cuando se trata de este tipo de algoritmo difícil de probar, iría por el TDD, donde construyes el algoritmo basado en pruebas,
TDD en resumen,
y repite el ciclo,
En esta situación particular,
Un aspecto importante en este método es que siempre debe agregar una prueba para el posible paso (donde divide los escenarios posibles en pruebas simples), y cuando cubre todos los escenarios posibles, el algoritmo generalmente evoluciona automáticamente.
Finalmente, debe agregar una o más pruebas de integración complicadas para ver si hay problemas imprevistos (como errores de desbordamiento de pila / errores de rendimiento cuando su gráfico es muy grande y cuando usa la recursividad)
fuente
Mi comprensión del problema, como se declaró originalmente y luego se actualizó mediante comentarios en la respuesta de Macke, incluye lo siguiente: 1) ambos tipos de borde (dependencias y conflictos) están dirigidos; 2) si dos nodos están conectados por un borde, no deben estar conectados por otro, incluso si es del otro tipo o al revés; 3) si se puede construir una ruta entre dos nodos mezclando bordes de diferentes tipos, entonces eso es un error, en lugar de una circunstancia que se ignora; 4) Si hay una ruta entre dos nodos usando bordes de un tipo, entonces puede que no haya otra ruta entre ellos usando bordes del otro tipo; 5) los ciclos de un tipo de borde único o de tipos de borde mixto no están permitidos (por una suposición en la aplicación, no estoy seguro de que los ciclos de solo conflicto sean un error, pero esta condición se puede eliminar, de lo contrario).
Además, supondré que la estructura de datos utilizada no evita que se expresen violaciones de estos requisitos (por ejemplo, un gráfico que viola la condición 2 no podría expresarse en un mapa de nodo a par (tipo, dirección) si el par de nodos siempre tiene el nodo menos numerado primero.) Si no se pueden expresar ciertos errores, reduce el número de casos a considerar.
En realidad, hay tres gráficos que se pueden considerar aquí: los dos de un solo tipo de borde y el gráfico mixto formado por la unión de uno de cada uno de los dos tipos. Puede usar esto para generar sistemáticamente todos los gráficos hasta cierto número de nodos. Primero, genere todos los gráficos posibles de N nodos que no tengan más de un borde entre dos pares de nodos ordenados (pares ordenados porque son gráficos dirigidos). Ahora tome todos los pares posibles de estos gráficos, uno que representa dependencias y el otro que representa conflictos, y formar la unión de cada par.
Si su estructura de datos no puede expresar violaciones de la condición 2, puede reducir significativamente los casos a considerar construyendo solo todos los gráficos de conflicto posibles que quepan dentro de los espacios de los gráficos de dependencia, o viceversa. De lo contrario, puede detectar violaciones de la condición 2 mientras forma la unión.
En un recorrido transversal del gráfico combinado desde el primer nodo, puede construir el conjunto de todas las rutas a cada nodo accesible, y al hacerlo, puede verificar si hay violaciones de todas las condiciones (para la detección del ciclo, puede usa el algoritmo de Tarjan ).
Solo tiene que considerar las rutas del primer nodo, incluso si el gráfico está desconectado, porque las rutas de cualquier otro nodo aparecerán como rutas del primer nodo en algún otro caso.
Si las rutas de borde mixto pueden simplemente ignorarse, en lugar de ser errores (condición 3), es suficiente considerar las gráficas de dependencia y conflicto de forma independiente, y verificar que si un nodo es accesible en uno, no está en el otro.
Si recuerda las rutas que se encuentran al examinar gráficos de nodos N-1, puede usarlos como punto de partida para generar y evaluar gráficos de N nodos.
Esto no genera múltiples bordes del mismo tipo entre nodos, pero podría extenderse para hacerlo. Sin embargo, esto aumentaría en gran medida el número de casos, por lo que sería mejor si el código que se está probando hace que sea imposible de representar, o si falla, filtra todos los casos de antemano.
La clave para escribir un oráculo como este es mantenerlo lo más simple posible, incluso si eso significa ser ineficiente, de modo que pueda establecer confianza en él (idealmente a través de argumentos para su corrección, respaldado por pruebas).
Una vez que tenga los medios para generar casos de prueba, y confíe en el oráculo que ha creado para separar con precisión lo bueno de lo malo, puede usar esto para conducir las pruebas automatizadas del código objetivo. Si eso no es factible, su siguiente mejor opción es revisar los resultados para casos distintivos. El oráculo puede clasificar los errores que encuentra y proporcionarle información sobre los casos aceptados, como el número y la longitud de las rutas de cada tipo, y si hay nodos al comienzo de ambos tipos de ruta, y esto podría ayudarlo a buscar casos que no haya visto antes.
fuente