web-dev-qa-db-fra.com

Que devez-vous tester avec des tests unitaires?

Je viens de quitter l'université et je rentre à l'université quelque part la semaine prochaine. Nous avons vu des tests unitaires, mais nous ne les avons pas beaucoup utilisés; et tout le monde en parle, alors j'ai pensé que je devrais peut-être en faire.

Le problème est que je ne sais pas quoi pour tester. Dois-je tester le cas commun? L'affaire Edge? Comment savoir qu'une fonction est correctement couverte?

J'ai toujours le terrible sentiment que même si un test prouve qu'une fonction fonctionne pour un certain cas, il est tout à fait inutile de prouver que la fonction fonctionne, point final.

128
zneak

Ma philosophie personnelle a donc été:

  1. Testez le cas commun de tout ce que vous pouvez. Cela vous indiquera quand ce code se cassera après que vous ayez apporté des modifications (ce qui est, à mon avis, le plus grand avantage des tests unitaires automatisés).
  2. Testez les cas Edge de quelques codes inhabituellement complexes qui, selon vous, comporteront probablement des erreurs.
  3. Chaque fois que vous trouvez un bogue, écrivez un cas de test pour le couvrir avant de le corriger
  4. Ajoutez des tests de cas Edge à du code moins critique chaque fois que quelqu'un a le temps de tuer.
124
Fishtoaster

Parmi la pléthore de réponses jusqu'à présent, personne n'a abordé partitionnement d'équivalence et analyse des valeurs limites , considérations essentielles dans la réponse à la question posée. Toutes les autres réponses, bien qu'utiles, sont qualitatives mais il est possible - et préférable - d'être quantitatif ici. @fishtoaster fournit des directives concrètes, jetant un coup d'œil sous les couvertures de la quantification des tests, mais le partitionnement d'équivalence et l'analyse des valeurs limites nous permettent de faire mieux.

Dans partitionnement d'équivalence , vous divisez l'ensemble de toutes les entrées possibles en groupes en fonction des résultats attendus. Toute entrée d'un groupe donnera des résultats équivalents, donc ces groupes sont appelés classes d'équivalence. (Notez que des résultats équivalents ne pas signifie des résultats identiques.)

À titre d'exemple simple, considérons un programme qui devrait transformer les caractères minuscules ASCII en caractères majuscules. Les autres caractères doivent subir une transformation d'identité, c'est-à-dire rester inchangés. Voici une ventilation possible en classes d'équivalence:

| # |  Equivalence class    | Input        | Output       | # test cases |
+------------------------------------------------------------------------+
| 1 | Lowercase letter      | a - z        | A - Z        | 26           |
| 2 | Uppercase letter      | A - Z        | A - Z        | 26           |
| 3 | Non-alphabetic chars  | 0-9!@#,/"... | 0-9!@#,/"... | 42           |
| 4 | Non-printable chars   | ^C,^S,TAB... | ^C,^S,TAB... | 34           |

La dernière colonne indique le nombre de cas de test si vous les énumérez tous. Techniquement, selon la règle 1 de @ fishtoaster, vous incluriez 52 cas de test - tous ceux des deux premières lignes données ci-dessus relèvent du "cas commun". La règle 2 de @ fishtoaster ajouterait également une partie ou la totalité des lignes 3 et 4 ci-dessus. Mais avec le partitionnement d'équivalence, tester tout un cas de test dans chaque classe d'équivalence est suffisant. Si vous choisissez "a" ou "g" ou "w", vous testez le même chemin de code. Ainsi, vous disposez d'un total de 4 cas de test au lieu de 52+.

L'analyse des valeurs limites recommande un léger raffinement: elle suggère essentiellement que tous les membres d'une classe d'équivalence ne sont pas, bien, équivalents. Autrement dit, les valeurs aux limites doivent également être considérées comme dignes d'un cas de test à part entière. (Une justification facile à cela est l'infâme erreur off-by-one !) Ainsi, pour chaque classe d'équivalence, vous pouvez avoir 3 entrées de test. En regardant le domaine d'entrée ci-dessus - et avec une certaine connaissance des valeurs ASCII - je pourrais trouver ces entrées de scénario de test:

| # | Input                | # test cases |
| 1 | a, w, z              | 3            |
| 2 | A, E, Z              | 3            |
| 3 | 0, 5, 9, !, @, *, ~  | 7            |
| 4 | nul, esc, space, del | 4            |

(Dès que vous obtenez plus de 3 valeurs limites, ce qui suggère que vous voudrez peut-être repenser vos délimitations de classe d'équivalence d'origine, mais cela était assez simple pour que je ne revienne pas à les réviser.) Ainsi, l'analyse des valeurs limites nous amène à juste 17 cas de test - avec une confiance élevée de couverture complète - contre 128 cas de test pour effectuer des tests exhaustifs. (Sans oublier que la combinatoire dicte qu'un test exhaustif est tout simplement irréalisable pour toute application réelle!)

68
Michael Sorens

Mon opinion n'est probablement pas trop populaire. Mais je vous suggère d'être économique avec les tests unitaires. Si vous avez trop de tests unitaires, vous pouvez facilement passer la moitié de votre temps ou plus à maintenir des tests plutôt qu'à coder.

Je vous suggère de passer des tests pour les choses que vous ressentez mal dans votre intestin ou les choses qui sont très cruciales et/ou élémentaires. Les tests unitaires à mon humble avis ne remplacent pas une bonne ingénierie et un codage défensif. Actuellement je travaille sur un projet plus ou moins inutilisable. Elle est vraiment stable mais pénible à refactoriser. En fait, personne n'a touché ce code en un an et la pile logicielle sur laquelle il est basé a 4 ans. Pourquoi? Parce qu'il est encombré de tests unitaires, pour être précis: tests unitaires et tests d'intégration automatisés. (Avez-vous déjà entendu parler du concombre et autres?) Et voici la meilleure partie: ce logiciel (encore) inutilisable a été développé par une entreprise dont les employés sont des pionniers sur la scène du développement piloté par les tests. :RÉ

Donc, ma suggestion est:

  • Commencez à écrire des tests après vous avez développé le squelette de base, sinon le refactoring peut être douloureux. En tant que développeur qui développe pour les autres, vous n'avez jamais les exigences dès le départ.

  • Assurez-vous que vos tests unitaires peuvent être effectués rapidement. Si vous avez des tests d'intégration (comme le concombre), c'est correct s'ils prennent un peu plus de temps. Mais les longs tests ne sont pas amusants, croyez-moi. (Les gens oublient toutes les raisons pour lesquelles le C++ est devenu moins populaire ...)

  • Laissez ce truc TDD aux experts TDD.

  • Et oui, parfois vous vous concentrez sur les cas Edge, parfois sur les cas courants, selon l'endroit où vous vous attendez à l'inattendu. Bien que si vous attendez toujours l'inattendu, vous devriez vraiment repenser votre flux de travail et votre discipline. ;-)

20
Philip

Si vous testez d'abord avec Test Driven Development, votre couverture sera supérieure à 90% ou plus, car vous n'ajouterez pas de fonctionnalité sans avoir d'abord écrit un test unitaire ayant échoué.

Si vous ajoutez des tests après coup, je ne saurais trop vous recommander d'obtenir une copie de Working Effectively With Legacy Code by Michael Feathers et jetez un œil à certains des des techniques pour ajouter des tests à votre code et des façons de refactoriser votre code pour le rendre plus testable.

8
Paddyslacker

Si vous commencez à suivre les pratiques Test Driven Development , ils vous trieront vous guideront tout au long du processus et sachant quoi tester viendra naturellement. Quelques points de départ:

Les tests viennent en premier

N'écrivez jamais de code avant d'écrire les tests. Voir Red-Green-Refactor-Repeat pour une explication.

Ecrire des tests de régression

Chaque fois que vous rencontrez un bug, écrivez un testcase et assurez-vous qu'il échoue . À moins que vous ne puissiez reproduire un bogue dans un testcase défaillant, vous ne l'avez pas vraiment trouvé.

Red-Green-Refactor-Repeat

Rouge : Commencez par écrire un test le plus élémentaire du comportement que vous essayez d'implémenter. Considérez cette étape comme l'écriture d'un exemple de code qui utilise la classe ou la fonction sur laquelle vous travaillez. Assurez-vous qu'il compile/n'a pas d'erreurs de syntaxe et qu'il échoue . Cela devrait être évident: vous n'avez écrit aucun code, il doit donc échouer, non? La chose importante à apprendre ici est qu'à moins que vous ne voyiez le test échouer au moins une fois, vous ne pouvez jamais être sûr que s'il passe, il le fait à cause de quelque chose que vous avez fait pour une raison fausse.

Vert : Écrivez le code le plus simple et stupide qui fait passer le test. N'essayez pas d'être intelligent. Même si vous voyez qu'il y a un cas Edge évident mais que le test tient compte, n'écrivez pas du code pour le gérer (mais n'oubliez pas le cas Edge: vous ' J'en aurai besoin plus tard). L'idée est que chaque morceau de code que vous écrivez, chaque if, chaque try: ... except: ... doit être justifié par un cas de test. Le code n'a pas besoin d'être élégant, rapide ou optimisé. Vous voulez juste que le test passe.

Refactor : Nettoyez votre code, obtenez les bons noms de méthode. Voyez si le test réussit toujours. Optimiser. Relancez le test.

Répétition : Vous vous souvenez du cas Edge que le test n'a pas couvert, non? Alors maintenant c'est son grand moment. Écrivez un testcase qui couvre cette situation, regardez-le échouer, écrivez du code, voyez-le passer, refactorisez.

Testez votre code

Vous travaillez sur un morceau de code spécifique, et c'est exactement ce que vous voulez tester. Cela signifie que vous ne devez pas tester les fonctions de bibliothèque, la bibliothèque standard ou votre compilateur. Essayez également d'éviter de tester le "monde". Cela comprend: appeler des API Web externes, des trucs gourmands en bases de données, etc. Chaque fois que vous pouvez essayer de le simuler (créez un objet qui suit la même interface, mais renvoie des données statiques prédéfinies).

6
Ryszard Szopa

Pour les tests unitaires, commencez par tester qu'il fait ce pour quoi il est conçu. Ce devrait être le tout premier cas que vous écrivez. Si une partie du design est "cela devrait lever une exception si vous passez en jonque", testez cela aussi car cela fait partie du design.

Commencez par ça. Au fur et à mesure que vous aurez l'expérience de faire les tests les plus élémentaires, vous commencerez à savoir si cela est suffisant ou non et commencerez à voir d'autres aspects de votre code qui nécessitent des tests.

3
Bryan Oakley

La réponse courante est de "tester tout ce qui pourrait éventuellement casser" .

Qu'est-ce qui est trop simple à casser? Champs de données, accesseurs de propriété à mort cérébrale et frais généraux similaires. Tout autre élément implémente probablement une partie identifiable d'une exigence et peut bénéficier d'un test.

Bien sûr, votre kilométrage - et les pratiques de votre environnement de travail - peuvent varier.

0
Jeffrey Hantin