Quelle est la meilleure façon d'organiser nos tests unitaires

18

Nous avons construit un nombre substantiel de tests unitaires pour notre programme principal au fil des ans. Plusieurs milliers. Le problème est que nous n'avons pas une idée claire de quels tests nous avons parce qu'il y en a tellement. Et c'est un problème parce que nous ne savons pas où nous sommes faibles sur les tests (ou où nous avons des doublons).

Notre application est un moteur de reporting. Vous pouvez donc avoir un modèle qui est utilisé pour tester l'analyse (lisons-nous toutes les propriétés de la table), fusionnant les données (avons-nous conservé les bonnes propriétés de la table dans la fusion), formatant la page finale (la table est-elle placée correctement sur la page ) et / ou le format de sortie (le fichier DOCX créé est-il correct).

Ajoutez à cela ce que nous devons tester. Prenez le remplissage autour d'une cellule de tableau (nous utilisons Word, Excel et PowerPoint pour la conception du rapport). Nous devons tester le remplissage entre les sauts de page, pour un tableau à l'intérieur d'une cellule, des cellules fusionnées verticalement, des cellules fusionnées horizontalement, une cellule fusionnée verticalement et horizontalement qui contient un tableau avec des cellules fusionnées verticalement et horizontalement dans le tableau interne, où ce tableau traverse une page.

Alors, dans quelle catégorie ce test entre-t-il? Remplissage de tableau, sauts de page, cellules imbriquées, cellules fusionnées verticalement, cellules fusionnées horizontalement ou autre chose?

Et comment documenter ces catégories, nommer les tests unitaires, etc.?

Mise à jour: Un certain nombre de personnes ont suggéré d'utiliser des outils de couverture pour vérifier que nous avons une couverture complète. Malheureusement, cela est d'une utilité limitée dans notre cas, car les bogues ont tendance à être dus à des combinaisons spécifiques, c'est donc le code qui a tous été testé, mais pas dans cette combinaison.

Par exemple, un client hier a commencé un signet Word à la fin d'une boucle forEach dans son modèle (un document Word) et l'a terminé au début de la boucle forEach suivante. Tout cela utilisait du code qui a des tests unitaires, mais nous n'avions pas pensé à la combinaison d'un modèle développant un signet commençant à être démarré 25 fois, puis terminé 10 fois (les deux boucles forEach avaient un nombre différent de lignes).

David Thielen
la source
1
Il semble que votre question soit vraiment: comment savons-nous que nous avons testé un scénario particulier?
Andy Wiesendanger
Oui! Et aussi où sont les tests pour des scénarios similaires. Et à partir de là, nous obtenons le besoin n ° 2 - lire ce qui est couvert nous aide à trouver ce que nous avons manqué.
David Thielen

Réponses:

13

En général, j'ai tendance à refléter l'arborescence source pour mes tests unitaires. Donc, si j'avais src / lib / fubar, j'aurais un test / lib / fubar qui contiendrait les tests unitaires pour fubar.

Cependant, ce que vous semblez décrire, ce sont des tests plus fonctionnels. Dans ce cas, j'aurais un tableau multidimensionnel qui énumérerait toutes vos conditions possibles. Ensuite, ceux qui n'ont aucun test sont soit absurdes, soit nécessitent un nouveau test. Vous pouvez bien sûr les placer ensuite dans des ensembles de suites de tests.

Sardathrion - Rétablir Monica
la source
Nous reflétons actuellement l'arbre source. Mais nous avons deux problèmes. Tout d'abord, pour le formatage des tableaux, il existe plus de 100 tests différents. Garder une trace de ce qui est exactement testé est devenu un problème. Deuxièmement, des domaines fonctionnels très différents doivent tester les tableaux - les analyseurs, la substitution des données, la mise en forme et la création du document de sortie. Je pense donc que vous avez raison, dans un sens, il s'agit de tests fonctionnels d'une propriété donnée.
David Thielen
Ce qui nous amène à la question: où stockons-nous le tableau des tests? Je pense à une feuille de calcul dans le répertoire source racine ???
David Thielen
Je le stockerais dans une feuille de calcul contrôlée par version dans le répertoire de test. Si vous avez beaucoup de choses à tester, la décomposer en méta-structures serait bénéfique. Essayez de penser en termes de ce qui est testé en général au lieu de quoi ou comment.
Sardathrion
7

La structure la plus courante semble être le miroir de répertoires srcand test.

src/module/class
test/module/class_test

Cependant, il y a une structure alternative que j'ai vue et que je pense maintenant meilleure.

src/module/class
src/module/class_test

Étant donné que le test unitaire a tendance à refléter la classe qu'ils testent, le fait de les placer dans le même répertoire permet un accès beaucoup plus facile aux fichiers afin que vous puissiez travailler côte à côte.

ming_codes
la source
2
Un inconvénient de l'ancienne approche est que chaque fois que vous décidez de modifier la structure de fichiers du projet, vous devez faire de même pour la structure des tests. Ce problème n'existe pas si les tests sont là où se trouve le code.
victor175
5

Dans .NET, j'ai tendance à refléter, ou du moins à approximer, la structure de l'espace de noms pour le code source dans les projets de test, sous un espace de noms principal "Tests.Unit" ou "Tests.Integration". Tous les tests unitaires vont dans un projet, avec la structure de base du code source répliquée sous forme de dossiers dans le projet. Idem pour les tests d'intégration. Ainsi, une solution simple pour un projet pourrait ressembler à ceci:

Solution
   MyProduct.Project1 (Project)
      Folder1 (Folder)
         ClassAA (Class def)
         ...
      Folder2
         ClassAB
         ...
      ClassAC
      ...
   MyProduct.Project2
      Folder1
         ClassBA
         ...
      ClassBB
      ...
   ...
   MyProduct.Tests.Unit
      Project1
         Folder1
            ClassAATests
            ClassAATests2 (possibly a different fixture setup)
         Folder2
            ClassABTests
         ClassACTests
      Project2
         Folder1
            ClassBATests
         ClassBBTests
      ...
   MyProduct.Tests.Integration
      Project1 (a folder named similarly to the project)
         Folder1 (replicate the folders/namespaces for that project beneath)
            ClassAATests
         Folder2
            ClassABTests
         ClassACTests
      Project2
         Folder1
            ClassBATests
         ClassBBTests

Pour tout AAT ou AEET codé avec un cadre de test unitaire, cela change un peu; généralement, ces tests reflètent un ensemble d'étapes, qui testeront la fonctionnalité d'un nouveau cas d'utilisation ou d'une nouvelle histoire. Je structure généralement ces tests dans un MyProduct.Tests.Acceptanceprojet en tant que tel, avec des tests pour chaque histoire, éventuellement regroupés par étape ou histoire "épique" à laquelle appartient l'histoire en cours de développement. Cependant, ce ne sont vraiment que des tests d'intégration, et donc si vous préférez structurer les tests d'une manière plus orientée objet plutôt que narrative, vous n'avez même pas besoin d'un MyProduct.Tests.Acceptanceprojet ou d'un projet similaire; jetez-les simplement MyProduct.Tests.Integrationsous la portée de l'objet de plus haut niveau testé.

KeithS
la source
3

Il n'y a aucune raison pour qu'un test unitaire soit dans une seule catégorie. Toutes les principales boîtes à outils de tests unitaires prennent en charge la création de suites de tests , qui regroupent les tests pour une catégorie particulière. Lorsqu'une zone de code particulière a été modifiée, le développeur doit exécuter cette suite en premier et souvent pour voir ce qui a cassé. Lorsqu'un test concerne le remplissage , les ruptures et l' imbrication, mettez-le dans les trois suites.

Cela dit, le but des tests unitaires est de les exécuter tout le temps, c'est-à-dire qu'ils doivent être petits et suffisamment rapides pour qu'il soit possible de les exécuter tous avant de commettre n'importe quel code. En d'autres termes, peu importe la catégorie d'un test, il doit être exécuté avant de s'engager de toute façon. Les suites ne sont vraiment qu'une béquille que vous utilisez si, pour une raison quelconque, vous ne pouvez pas écrire des tests aussi rapides qu'ils le devraient.

En ce qui concerne la couverture, il existe de très bons outils de couverture qui vous indiquent le pourcentage de lignes réellement exercées lors de l'exécution de vos tests - c'est un indicateur évident du type de tests qui vous manque encore.

Quant à la dénomination, il n'y a pas de valeur particulière à consacrer des efforts aux noms des tests unitaires. Tout ce que vous voulez entendre de vos tests est "5235 des 5235 tests réussis". Lorsqu'un test échoue, ce que vous lisez n'est pas son nom, mais le message , par exemple la chaîne dans le assert()qui implémente votre critique de réussite. Le message doit être suffisamment informatif pour que vous ayez une idée de ce qui ne va pas sans lire le corps du test. Le nom n'a pas d'importance par rapport à cela.

Kilian Foth
la source
D'accord à 100% sur tout ce que vous dites (notre machine de construction exécute tous les tests lors de l'enregistrement). Notre gros problème est de suivre ce que nous testons. Et la couverture du code n'est pas très utile (voir la mise à jour ci-dessus).
David Thielen
1

La traçabilité est un moyen de savoir si vous êtes faible aux tests. Habituellement, pour les tests, cela prend la forme d'une couverture.

Le but est de mesurer quelles parties du code sont exercées par vos tests, afin que vous puissiez voir le code qui n'est pas couvert par vos tests. C'est à vous (et à l'outil de couverture) de définir ce qu'est une "partie de code". Le moins est la couverture des succursales.

mouviciel
la source