Comment le code de test unitaire C ++ doit-il être organisé pour une efficacité maximale du test unitaire?


47
  • Cette question ne concerne pas les cadres de tests unitaires.
  • Cette question ne concerne pas la rédaction de tests unitaires.
  • Cette question concerne l' endroit où placer le code UT écrit et comment / quand / où le compiler et l'exécuter.

En travaillant efficacement avec Legacy Code , Michael Feathers affirme que

bons tests unitaires ... courez vite

et cela

Un test unitaire qui prend 1 / 10ème de seconde est un test unitaire lent.

Je pense que ces définitions ont un sens. Je pense aussi que cela implique que vous devez conserver un ensemble de tests unitaires et un ensemble de tests de code qui prennent plus longtemps séparément, mais je suppose que c'est le prix que vous payez pour appeler seulement un test unitaire s'il fonctionne (très) rapidement .

Il est évident que le problème en C ++ est que pour « exécuter » votre unité de test ( de s ), vous devez:

  1. Modifiez votre code (production ou test unitaire, en fonction du "cycle" dans lequel vous vous trouvez)
  2. Compiler
  3. Lien
  4. Unité de démarrage d' essai Executable ( s )

Edit (après un vote bizarre) : Avant d’entrer dans les détails, je vais essayer de résumer le point ici:

Comment organiser efficacement le code de test unitaire C ++, de sorte qu'il soit à la fois efficace de modifier le code (de test) et de l'exécuter?


Le premier problème consiste alors à décider placer le code de test unitaire afin que:

  • il est "naturel" de l'éditer et de l'afficher en combinaison avec le code de production associé.
  • il est facile / rapide de démarrer le cycle de compilation pour l'unité en cours de modification

Le deuxième problème, lié, est alors de savoir quoi compiler pour que le retour d’information soit instantané.

Options extrêmes:

  • Chaque unité Unit-Test-Test-Unit réside dans un fichier cpp distinct et ce fichier cpp est compilé + lié séparément (avec le fichier unité de code source qu’il teste) à un seul exécutable qui exécute ensuite ce test unitaire.
    • (+) Ceci minimise le temps de démarrage (compilation + lien!) Pour la même unité de test.
    • (+) Le test est très rapide car il ne teste qu'une seule unité.
    • (-) L'exécution de toute la suite nécessitera un nombre incroyable de processus. Peut être un problème à gérer.
    • (-) Les frais généraux liés aux débuts du processus deviendront visibles
  • L’autre côté serait d’avoir - toujours - un fichier cpp par test, mais tous les fichiers cpp de test (ainsi que le code qu’ils testent!) Sont liés dans un seul exécutable (par module / par projet / choisissez votre choix).
    • (+) Le temps de compilation serait toujours correct, car seul le code modifié sera compilé.
    • (+) Exécuter la suite entière est facile, puisqu'il n'y a qu'un seul exe à exécuter.
    • (-) Il faudra du temps pour que la suite soit liée, car chaque recompilation d’un objet déclenchera une nouvelle liaison.
    • (-) (?) La combinaison prendra plus de temps, bien que si tous les tests unitaires sont rapides, le temps sera correct.

Alors, comment sont gérés les tests unitaires C ++ du monde réel ? Si je ne fais que ça tous les soirs / toutes les heures, la deuxième partie importe peu, mais la première, à savoir comment "coupler" le code UT au code de production, de sorte qu'il soit "naturel" pour les développeurs de conserver les deux Je pense que la concentration compte toujours. (Et si les développeurs ont le code UT dans le focus, ils voudront le lancer, ce qui nous ramène à la deuxième partie.)

Histoires du monde réel et expérience appréciée!

Remarques:

  • Cette question laisse intentionnellement une plate-forme et un système de marque / projet non spécifiés.
  • Questions marquées UT & C ++ est un bon point de départ, mais malheureusement, trop de questions, et surtout de réponses, sont trop centrées sur les détails ou sur des cadres spécifiques.
  • Il y a quelque temps, j'ai répondu à une question similaire sur la structure des tests unitaires de boost. Je trouve que cette structure manque pour les "vrais" tests unitaires rapides. Et je trouve l’autre question trop étroite, d’où cette nouvelle question.

6
Le langage C ++ introduit une complication supplémentaire: déplacer le plus d’erreurs possible pour compiler le temps - une bonne suite de tests unitaires doit souvent pouvoir vérifier que certains usages échouent à la compilation.

3
@Closers: Pourriez-vous s'il vous plaît fournir les arguments qui vous ont amené à fermer cette question?
Luc Touraille

Je ne vois pas très bien pourquoi cela a dû être fermé. :-(Où êtes-vous supposé chercher des réponses à de telles questions si ce n'est dans ce forum?

2
@ Joe: Pourquoi ai-je besoin d'un test unitaire pour déterminer si quelque chose se compilera quand le compilateur me le dira de toute façon?
David Thornley

2
@ David: Parce que vous voulez vous assurer qu'il ne compile pas . Un exemple rapide serait un objet de pipeline qui accepte une entrée et produit une sortie Pipeline<A,B>.connect(Pipeline<B,C>)doit être compilé alors que Pipeline<A,B>.connect(Pipeline<C,D>)ne doit pas être compilé: Le type de sortie du premier étage est incompatible avec le type d’entrée du deuxième étage.
sebastiangeiger

Réponses:


6

Nous avons tous les tests unitaires (pour un module) dans un exécutable. Les tests sont mis en groupes. Je peux exécuter un seul test (ou des tests) ou un groupe de tests en spécifiant un nom (test / groupe) sur la ligne de commande du testeur. Le système de compilation peut exécuter le groupe "Build", le département de test peut exécuter "Tous". Le développeur peut mettre des tests dans un groupe tel que "BUG1234", 1234 étant le numéro de suivi du problème sur lequel il travaille.


6

Tout d'abord, je ne suis pas d'accord avec "1) Modifiez votre code (de production) et votre test unitaire". Vous ne devez en modifier qu'un à la fois, sinon, si le résultat change, vous ne saurez pas lequel en est la cause.

J'aime mettre des tests unitaires dans une arborescence de répertoires qui ombrage l'arborescence principale. Si j'ai /sources/componentA/alpha/foo.ccet /objects/componentA/beta/foo.o, alors je veux quelque chose comme /UTest_sources/componentA/alpha/test_foo.ccet /UTest_objects/componentA/beta/test_foo.o. J'utilise le même arbre d'ombre pour les objets stub / mock et quelles que soient les autres sources nécessaires aux tests. Il y aura des cas extrêmes, mais ce schéma simplifie beaucoup les choses. Une bonne macro d'éditeur peut facilement extraire la source de test aux côtés de la source de sujet. Un bon système de compilation (par exemple, GNUMake) peut compiler les deux et exécuter le test avec une seule commande (par exemple make test_foo), et peut gérer un nombre incroyable de tels processus - uniquement ceux dont les sources ont changé depuis le dernier test - assez facilement (j'ai jamais trouvé la surcharge de démarrage de ces processus être un problème, c’est O (N)).

Dans le même cadre, vous pouvez avoir des tests à plus grande échelle (et non plus des tests unitaires) qui relient plusieurs objets et exécutent de nombreux tests. L'astuce consiste à trier ces tests en fonction du temps nécessaire à leur construction / exécution et à les intégrer à votre programme quotidien en conséquence. Exécutez le test d'une seconde ou moins chaque fois que vous en avez envie; commencer le test de dix secondes et s'étirer; test de cinq minutes et faire une pause; test d'une demi-heure et aller pour le déjeuner; test de six heures et rentrez chez vous. Si vous constatez que vous perdez beaucoup de temps, par exemple, si vous reliez un test énorme après avoir modifié un seul petit fichier, vous le faites mal. Même si la liaison était instantanée, vous exécuteriez toujours un long test n'a pas été appelé.


ad (1) - oui, cela a été mal rédigé
Martin Ba
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.