Comment vous codez unité test à l'aide de structures graphique?


18

J'écris du code (récursif) qui navigue dans un graphe de dépendances recherche des cycles ou des contradictions dans les dépendances. Cependant, je ne suis pas sûr à l'unité approcher tester cela. Le problème est que l'une de nos principales préoccupations est que le code gère toutes les structures de graphiques intéressantes qui peuvent survenir et s'assure que tous les nœuds seront traités de manière appropriée.

Bien qu'une couverture de ligne ou de branche à 100% soit généralement suffisante pour être sûr que certains codes fonctionnent, il semble que même avec une couverture de chemin à 100%, vous ayez encore des doutes.

Alors, comment procéder pour sélectionner des structures de graphes pour les cas de test afin d'avoir l'assurance que leur code pourrait gérer toutes les permutations imaginables que vous trouverez dans les données du monde réel.


PS- Si c'est important, tous les bords de mon graphique sont étiquetés "doit avoir" xor "ne peut pas avoir" et il n'y a pas de cycles triviaux, et il n'y a qu'un seul bord entre deux nœuds.


PPS- L'énoncé de problème supplémentaire a été enregistré par l'auteur de la question dans un commentaire ci - dessous:

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'.


13
Même manière que vous test unitaire toute autre méthode. Vous identifiez tous les cas de test « intéressants » pour chaque méthode des tests unitaires écrire pour eux. Dans votre cas, vous devez créer des graphiques de dépendance en conserve pour chacune des structures graphique « intéressantes ».
trempent

@Dunk Nous continuons à penser que nous avons tous les délicates couverts puis on se rend compte qu'une certaine structure provoque des problèmes nous n'étions pas considérer avant. Tests Chaque délicat que nous pouvons penser est ce que nous faisons, ce que je suis l' espoir de trouver est des lignes directrices / procédures à générer gênants exemples peut - être en utilisant réductibilité des formes fondamentales , etc.
Sled

6
Tel est le problème avec toute forme de test. Tout ce que vous savez est que les tests que vous avez pensé du travail. Cela ne signifie pas que votre sw est exempt d'erreurs simplement parce que vos tests réussissent. Chaque projet a ce même problème. Je suis dans les dernières étapes de la livraison de mon projet actuel afin que nous puissions commencer la fabrication. Les types d'erreurs que nous rencontrons maintenant ont tendance à être plutôt obscurs. Par exemple, où le matériel fonctionne toujours à spec mais à peine et lorsqu'il est combiné avec d' autres matériels simultanément avec la même question se produisent alors des problèmes; mais parfois :( Le sw est bien testé , mais on ne pensait pas tout
trempent

Ce que vous décrivez ressemble plus à un test d'intégration qu'à un test unitaire. Les tests unitaires garantiraient qu'une méthode est capable de trouver les cercles dans un graphique. D'autres tests unitaires garantiraient qu'un cercle spécifique d'un graphe spécifique est géré par la classe testée.
SpaceTrucker

Détection de cycle est un sujet bien couvert (voir Knuth, aussi des réponses ci - dessous) et les solutions ne comporte pas un grand nombre de cas spéciaux, vous devez d' abord déterminer ce qui fait votre problème comme celui - ci. Est - ce en raison des contradictions que vous évoquez? Dans ce cas, nous avons besoin de plus d' informations sur eux. Si elle est le résultat d'options de mise en œuvre, vous devez refactoriser, peut - être en grand. Fondamentalement, cela est un problème de conception , vous devez penser votre chemin à travers, TDD est fausse route qui vous fera pénétrer dans les méandres avant de morts fin.

Réponses:


5

Nous continuons de penser que nous avons tous ceux qui sont délicats couverts, puis nous nous rendons compte qu'une certaine structure cause des problèmes que nous n'avions pas envisagés auparavant.

Cela semble être un bon début. Je suppose que vous avez déjà essayé d'appliquer des techniques classiques comme analyse des valeurs limites ou partitionnement équivalence , car vous tests basés sur la couverture déjà parlé. Après vous avez investi beaucoup de temps dans la construction de cas bon test, vous arriverez à un point où vous, votre équipe et aussi testeurs (si vous avez les) à court d'idées. Et c'est l'endroit où vous devez quitter le chemin de l' unité test et commencer à tester avec des données beaucoup de monde réel que vous pouvez.

Peut-être que vous devez écrire des outils ou des programmes supplémentaires juste pour cette partie du processus. La partie difficile ici est probablement de vérifier l'exactitude de la sortie de vos programmes, lorsque vous mettez dix mille graphiques différents dans votre programme, comment saurez-vous si votre programme produit toujours la sortie correcte? De toute évidence, vous ne pouvez pas vérifier manuellement. Donc, si vous êtes chanceux, vous pourrez peut-être effectuer une deuxième implémentation très simple de votre contrôle de dépendance, qui pourrait ne pas répondre à vos attentes de performances, mais est plus facile à vérifier que votre algorithme d'origine. Vous devriez aussi essayer d'intégrer beaucoup de vérifications de vraisemblance directement dans votre programme (par exemple,

Enfin, apprenez à accepter que chaque test ne peut que prouver l'existence de bugs, mais pas l'absence de bugs.


5

1. Génération de tests randomisés

Ecrire un algorithme qui génère des graphiques, l'ont générer quelques centaines (ou plus) des graphes aléatoires et jeter chacun à votre algorithme.

Conservez la graine aléatoire des graphiques qui provoquent des échecs intéressants et ajoutez-les en tant que tests unitaires.

2. Pièces difficiles à coder en dur

Quelques graphiques structures que vous savez sont difficiles, vous pouvez coder tout de suite, ou d'écrire un code qui combine eux et les pousse à votre algorithme.

3. Générer une liste exhaustive

Mais, si vous voulez être sûr « le code pourrait gérer toutes les permutations imaginables que vous trouverez dans les données du monde réel. », Vous devez générer ces données à partir de graines non au hasard, mais en marchant bien que toutes les permutations. (Cela se fait lors du test de systèmes de signalisation ferroviaire de métro, et vous donne d'énormes quantités de cas qui prend les âges à tester. Pour le métro de métro, le système est limité, donc il y a une limite supérieure au nombre de permutations. Je ne sais pas comment votre cas applique)


L'intervenant a écrit qu'ils ne sont pas en mesure de dire s'ils ont pris en compte tous les cas, ce qui implique qu'ils n'ont aucun moyen de les énumérer. Jusqu'à ce qu'ils comprennent le domaine du problème assez bien pour le faire, comment tester est une question discutable.
sdenham

@sdenham Comment allez - vous à quelque chose qui a Énumérer literrally un nombre infini de combinaisons possibles valides? J'espérais trouver quelque chose dans le sens de "ce sont les structures graphiques les plus délicates qui attraperont souvent des bugs dans votre implémentation". Je comprends assez bien le domaine comme il est simple: 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'.Le domaine n'est pas la question.
Traîneau du

@ArtB: Merci pour votre clarification du problème. Comme vous l'avez dit, il n'y a pas plus d'une arête entre deux sommets et exclut apparemment les chemins avec des cycles (ou au moins plus d'un passage autour d'un cycle), alors au moins nous savons qu'il n'y a pas littéralement un nombre infini de combinaisons valides possibles, ce qui est un progrès. Notez que savoir comment énumérer toutes les possibilités n'est pas la même chose que de dire que vous devez le faire, car cela pourrait être un point de départ pour faire un argument pour l'exactitude, qui à son tour peut guider les tests. Je donnerai plus la pensée ...
sdenham

@ArtB: vous devez modifier la question pour inclure la mise à jour de l'énoncé du problème que vous avez donné ici. En outre, il peut aider à affirmer que ces sont des bords orientés (si tel est le cas), et si un cycle serait considéré comme une erreur dans le graphique, plutôt que d' une situation les besoins de l' algorithme à manipuler.
sdenham

4

Aucun test ne pourra être suffisant dans ce cas, pas même des tonnes de données réelles ou de fuzzing. Une couverture de code à 100%, voire une couverture de chemin à 100% est insuffisante pour tester les fonctions récursives.

Soit la fonction récursive se lève une preuve formelle (ne devrait pas être si difficile dans ce cas), ou il ne fonctionne pas. Si le code est trop entrelacé avec du code spécifique à l'application pour exclure les effets secondaires, c'est par où commencer.

L'algorithme lui-même ressemble à un algorithme simple d'inondation, semblable à un simple large recherche d'abord, avec l'ajout d'une liste noire qui ne doit pas Intersection avec la liste des noeuds visités, exécutez de tous les nœuds.

foreach nodes as node
    foreach nodes as tmp
        tmp.status = unmarked

    tovisit = []
    tovisit.push(node)
    node.status = required

    while |tovisit| > 0 do
        next = tovisit.pop()
        foreach next.requires as requirement
            if requirement.status = unmarked
                tovisit.push(requirement)
                requirement.status = required
            else if requirement.status = blacklisted
                return false
        foreach next.collides as collision
            if collision.status = unmarked
                requirement.status = blacklisted
            else if requirement.status = required
                return false
return true

Cet algorithme itératif satisfait à la condition qu'aucune dépendance peut être nécessaire et mis à l'index en même temps, pour les graphiques de la structure arbitraire, à partir de tout artefact arbitraire dans lequel l'artefact de départ est toujours nécessaire.

Bien qu'il peut ou ne peut pas être aussi rapide que votre propre implémentation, il peut être prouvé qu'elle se termine pour tous les cas (comme pour chaque itération de la boucle externe chaque élément ne peut être poussé une fois sur la tovisitfile d' attente), il inonde l'ensemble accessible graphique (preuve inductive), et il détecte tous les cas où un artefact est appelé à être requis et une liste noire en même temps, à partir de chaque noeud.

Si vous pouvez montrer que votre propre implémentation a les mêmes caractéristiques, vous pouvez prouver l'exactitude sans entraîner de tests unitaires. Seules les méthodes de base pour pousser et sauter des files d'attente, en comptant la longueur de la file d'attente, itérer sur les propriétés et ont tous besoin d'être testés et se sont révélés exempts d'effets secondaires.

EDIT: Ce que cet algorithme ne prouve pas, c'est que votre graphique est exempt de cycles. Des graphes acycliques sont un sujet bien documenté, donc trouver un algorithme tout fait pour prouver cette propriété devrait être facile aussi bien.

Comme vous pouvez le voir, il n'est pas du tout nécessaire de réinventer la roue.


3

Vous utilisez des expressions comme «toutes les structures de graphiques intéressantes» et «gérées de manière appropriée». Sauf si vous avez les moyens de tester votre code contre toutes ces structures et déterminer si le code gère le graphique correctement, vous ne pouvez utiliser des outils tels que l'analyse de couverture de test.

Je vous suggère de commencer par la recherche et les essais avec un certain nombre de structures de graphiques intéressants et déterminer le traitement approprié serait et que le code fait ça. Ensuite, vous pouvez commencer ces graphiques en perturbant a) des graphiques brisés qui violent les règles ou b) des graphiques pas si intéressants qui ont des problèmes; voir si votre code ne parvient pas à les gérer correctement.


Bien qu'il s'agisse d'une bonne approche pour les tests, elle ne résout pas le problème central de la question: comment garantir que tous les cas sont couverts. Je pense que cela nécessitera plus d'analyse et peut-être une refactorisation - voir ma question ci-dessus.
sdenham


2

En ce qui concerne ce type d'algorithme difficile à tester, j'opterais pour le TDD, où vous construisez l'algorithme basé sur des tests,

TDD bref,

  • écrire le test
  • voir ça échoue
  • modifier le code
  • assurez-vous que tous les tests réussissent
  • refactor

et répéter le cycle,

Dans cette situation particulière,

  1. Le premier test serait un graphe à nœud unique où l'algorithme ne devrait retourner aucun cycle
  2. Le deuxième serait un graphique à trois nœuds sans cycle où l'algorithme ne devrait retourner aucun cycle
  3. La prochaine serait d'utiliser un graphe à trois nœuds avec un cycle où l'algorithme ne devrait retourner aucun cycle
  4. Maintenant, vous pouvez le tester contre un cycle un peu plus complexe en fonction des possibilités

Un aspect important de cette méthode est que vous devez toujours ajouter un test pour l'étape possible (où vous divisez les scénarios possibles en tests simples), et lorsque vous couvrez tous les scénarios possibles, l'algorithme évolue généralement automatiquement.

Enfin, vous devez ajouter un ou plusieurs tests d'intégration compliqués pour voir s'il y a des problèmes imprévus (tels que des erreurs de débordement de pile / des erreurs de performances lorsque votre graphique est très volumineux et lorsque vous utilisez la récursivité)


2

Ma compréhension du problème, tel qu'indiqué à l'origine puis mis à jour par les commentaires sous la réponse de Macke, comprend les éléments suivants: 1) les deux types de bord (dépendances et conflits) sont dirigés; 2) si deux nœuds sont connectés par un bord, ils ne doivent pas être connectés par un autre, même s'il est de l'autre type ou inversé; 3) si un chemin entre deux nœuds peut être construit en mélangeant des bords de différents types, alors c'est une erreur, plutôt qu'une circonstance qui est ignorée; 4) S'il y a un chemin entre deux nœuds utilisant des bords d'un type, alors il ne peut pas y avoir un autre chemin entre eux utilisant des bords d'un autre type; 5) cycles de soit un seul type de bord ou types bord mixtes ne sont pas autorisés (à partir d'une estimation à l'application, je ne suis pas sûr que les cycles de conflit ne sont une erreur, mais cette condition peut être enlevé, sinon.)

De plus, je suppose que la structure de données utilisée n'empêche pas que des violations de ces exigences soient exprimées (par exemple, un graphique violant la condition 2 ne peut pas être exprimé dans une carte de la paire de nœuds à (type, direction) si la paire de nœuds est toujours a le noeud moins numéroté en premier.) Si certaines erreurs ne peuvent être exprimées, il réduit à considérer le nombre de cas.

Il y a en fait trois graphiques qui peuvent être considérés ici: les deux exclusivement un type de bord, et le graphique mixte formé par l'union d'un de chacun des deux types. Vous pouvez l'utiliser pour générer systématiquement tous les graphiques jusqu'à un certain nombre de nœuds. Générez d'abord tous les graphiques possibles de N nœuds n'ayant pas plus d'un bord entre deux paires ordonnées de nœuds (paires ordonnées car ce sont des graphiques dirigés.) Prenez maintenant toutes les paires possibles de ces graphiques, l'une représentant les dépendances et l'autre représentant les conflits, et former l'union de chaque paire.

Si votre structure de données ne peut pas exprimer les violations de la condition 2, vous pouvez réduire considérablement les cas à considérer en construisant uniquement tous les graphiques de conflit possibles qui tiennent dans les espaces des graphiques de dépendance, ou vice-versa. Dans le cas contraire, vous pouvez détecter les violations de l'état 2 tout en formant l'union.

Sur une traversée en largeur du graphique combiné à partir du premier nœud, vous pouvez créer l'ensemble de tous les chemins vers chaque nœud accessible et, ce faisant, vous pouvez vérifier les violations de toutes les conditions (pour la détection de cycle, vous pouvez utiliser l'algorithme de Tarjan .)

Il vous suffit de considérer les chemins du premier noeud, même si le graphique est déconnecté, car les chemins de tout autre noeud apparaissent comme les chemins du premier noeud dans un autre cas.

Si les chemins à bords mixtes peuvent simplement être ignorés, plutôt que d'être des erreurs (condition 3), il suffit de considérer les graphiques de dépendance et de conflit indépendamment et de vérifier que si un nœud est accessible dans l'un, il ne l'est pas dans l'autre.

Si vous vous rappelez les chemins trouvés dans l'examen des graphiques des nœuds N-1, vous pouvez les utiliser comme point de départ pour générer et graphiques évaluation des noeuds N.

Cela ne génère pas de multiples arêtes du même type entre les nœuds, mais il pourrait être étendu à le faire. Cela augmenter considérablement le nombre de cas cependant, il serait préférable que l'être de code testé a fait cette impossible de représenter ou, à défaut, filtré tous les cas au préalable.

La clé pour écrire un oracle comme celui-ci est de le garder aussi simple que possible, même si cela signifie être inefficace, afin que vous puissiez lui faire confiance (idéalement par le biais d'arguments pour son exactitude, étayés par des tests.)

Une fois que vous avez les moyens de générer des cas de test et que vous faites confiance à l'oracle que vous avez créé pour séparer avec précision le bon du mauvais, vous pouvez l'utiliser pour piloter le test automatisé du code cible. Si cela n'est pas possible, votre prochaine meilleure option consiste à passer en revue les résultats pour les cas distinctifs. L'oracle peut classer les erreurs qu'il trouve et vous donner des informations sur les cas acceptés, tels que le nombre et la longueur des chemins de chaque type, et s'il y a des nœuds au début des deux types de chemin, et cela pourrait vous aider à rechercher des cas que vous n'avez jamais vus auparavant.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.