Que devriez-vous tester avec des tests unitaires?


122

Je viens de sortir de l'université et commence 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 je me suis dit que je devrais peut-être en faire.

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

J'ai toujours le sentiment terrible que, si un test prouve qu'une fonction fonctionne pour un cas donné, il est totalement inutile de prouver que la fonction fonctionne, point à la ligne.


Jetez un coup d'œil au blog de Roy Osherove . Vous y trouverez de nombreuses informations sur les tests unitaires, notamment des vidéos. Il a également écrit un livre intitulé "L'art des tests unitaires" qui est très bon.
Piers Myers

9
Je me demande ce que vous en pensez après presque 5 ans? Parce que, de plus en plus, j’ai le sentiment que les gens devraient mieux savoir «ce qu’il ne faut pas faire d’un test unitaire» de nos jours. Le développement axé sur le comportement a évolué à partir des questions que vous avez posées.
Remigijus Pankevičius

Réponses:


121

Ma philosophie personnelle a donc été:

  1. Testez le cas commun de tout ce que vous pouvez. Cela vous indiquera quand ce code sera cassé après avoir apporté une modification (ce qui, à mon avis, constitue le principal avantage des tests unitaires automatisés).
  2. Testez les cas extrêmes de quelques codes inhabituellement complexes qui, selon vous, comporteront probablement des erreurs.
  3. Chaque fois que vous trouvez un bogue, écrivez un scénario de test pour le couvrir avant de le corriger.
  4. Ajoutez des tests de cas marginaux à du code moins critique chaque fois que quelqu'un a le temps de tuer.

1
Merci pour cela, je me débattais ici avec les mêmes questions que le PO.
Stephen

5
+1, mais je voudrais également tester les cas extrêmes de toutes les fonctions de type bibliothèque / utilitaire pour vous assurer de disposer d'une API logique. Par exemple, que se passe-t-il lorsqu'un null est passé Qu'en est-il des entrées vides? Cela vous aidera à vous assurer que votre conception est logique et à documenter le comportement de la casse d'angle.
mikera

7
N ° 3 semble être une réponse très solide car c'est un exemple concret de la façon dont un test unitaire aurait pu aider. Si ça casse une fois, ça peut casser encore.
Ryan Griffith

Comme je viens de commencer, je trouve que je ne suis pas très créatif dans la conception de tests. Donc, je les utilise comme n ° 3 ci-dessus, ce qui assure la tranquillité d'esprit que ces bugs ne seront plus jamais détectés.
ankush981

Votre réponse a été présentée dans cet article médiatique
BugHunterUK

67

Parmi la pléthore de réponses à ce jour, personne n’a abordé le partitionnement de l’équivalence et l’ 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 quantitatives ici. @fishtoaster fournit des indications concrètes, jetant un coup d'œil sous le couvert de la quantification de test, mais le partitionnement par équivalence et l'analyse des valeurs limites nous permettent de faire mieux.

Dans la partition d'équivalence , vous divisez l'ensemble des entrées possibles en groupes en fonction des résultats attendus. Toute entrée d'un groupe donnera des résultats équivalents, de tels groupes s'appellent des classes d'équivalence . (Notez que des résultats équivalents ne signifient pas des résultats identiques.)

Comme exemple simple, considérons un programme qui devrait transformer des caractères ASCII minuscules en caractères majuscules. Les autres personnages 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 tests si vous les énumérez tous. Techniquement, selon la règle 1 de @ fishtoaster, vous incluez 52 cas tests - tous ceux des deux premières lignes indiquées ci-dessus entrent dans le "cas commun". La règle 2 de @ fishtoaster ajoute également tout ou partie des rangées 3 et 4 ci-dessus. Toutefois, avec le test de partitionnement par équivalence, un seul 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 avez un total de 4 cas de test au lieu de 52+.

L'analyse de la valeur limite recommande un léger raffinement: elle suggère essentiellement que tous les membres d'une classe d'équivalence ne sont pas équivalents. C'est-à-dire que les valeurs aux limites doivent également être considérées comme dignes d'un cas test à part entière. (Une erreur simple en est la fameuse 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 arriver avec 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, cela suggère que vous voudriez peut-être repenser vos délinéations de classes d'équivalence d'origine, mais c'était assez simple pour que je ne revienne pas les réviser.) Ainsi, l'analyse des valeurs limites nous amène à 17 cas de test - avec une confiance élevée en couverture complète - comparés à 128 cas de test exhaustifs. (Sans compter que la combinatoire indique qu'un test exhaustif est tout simplement irréalisable pour toute application réelle!)


3
+1 C'est exactement comme ça que j'écris intuitivement mes tests. Maintenant, je peux mettre un nom dessus :) Merci de partager cela.
guillaume31

+1 pour "les réponses qualitatives sont utiles, mais il est possible - et préférable - d'être quantitatif"
Jimmy Breck-McKye

Je pense que c'est une bonne réponse si la directive est "comment puis-je obtenir une bonne couverture avec mes tests". Je pense qu'il serait utile de trouver une approche pragmatique au-dessus de cela - l'objectif est-il que chaque branche de chaque élément de logique dans chaque couche soit testée de manière approfondie de cette manière?
Kieren Johnstone

18

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 plus de la moitié de votre temps à entretenir les tests plutôt qu’à coder.

Je vous suggère d’écrire des tests sur des choses qui vous font mal ou sur des choses qui sont très importantes et / ou élémentaires. Les tests unitaires IMHO ne remplacent pas une bonne ingénierie et un codage défensif. Actuellement, je travaille sur un projet plus ou moins inutilisable. C'est vraiment stable mais ça fait mal au refactor. En fait, personne n’a touché à ce code en un an et la pile de logiciels 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. (Vous avez déjà entendu parler de concombre, etc.) Et voici la meilleure partie: ce logiciel (pourtant) 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 avoir développé le squelette de base, sinon la refactorisation peut être douloureuse. En tant que développeur qui développe pour les autres, vous n’obtenez jamais les exigences requises 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 bon s'ils prennent un peu plus longtemps. 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 matériel TDD aux experts TDD.

  • Et oui, parfois vous vous concentrez sur les cas extrêmes, parfois sur les cas courants, selon l'endroit où vous vous attendez à l'inattendu. Cependant, si vous attendez toujours l'imprévu, vous devez vraiment repenser votre flux de travail et votre discipline. ;-)


2
Pouvez-vous donner plus de détails sur les raisons pour lesquelles les tests rendent ce logiciel difficile à refactoriser?
Mike Partridge

6
Big +1. Avec des murs de tests unitaires qui testent l'implémentation au lieu des règles, tout changement nécessite 2-3 fois plus
TheLQ

9
Comme un code de production mal écrit, les tests unitaires mal écrits sont difficiles à maintenir. "Trop de tests unitaires" ressemble à une incapacité à rester au sec; chaque test doit aborder / prouver une partie spécifique du système.
Allan

1
Chaque test unitaire doit vérifier une chose, il n’ya donc pas trop de tests unitaires, mais des tests manquants. Si vos tests unitaires sont complexes, c'est un autre problème.
graffic

1
-1: Je pense que ce post est mal écrit. Plusieurs choses ont été mentionnées et je ne sais pas comment elles se rapportent toutes. Si le but de la réponse est "soyez économique", alors comment votre exemple se rapporte-t-il? Il semble que votre exemple de situation (bien que réel) comporte de mauvais tests unitaires. Veuillez expliquer les leçons que je suis supposé tirer de cela et en quoi cela m'aide à être économique. En outre, honnêtement, je ne sais tout simplement pas ce que vous voulez dire quand vous dites Leave this TDD stuff to the TDD-experts.
Alexander Bird

8

Si vous testez d'abord avec Test Driven Development, votre couverture sera supérieure ou égale à 90%, car vous n'ajouterez pas de fonctionnalité sans avoir au préalable écrit un test d'unité défaillant.

Si vous ajoutez des tests après coup, je ne saurais trop vous recommander d'obtenir une copie de Working Efficient With Legacy Code de Michael Feathers et de jeter un coup d'œil à certaines des techniques permettant d'ajouter des tests à votre code et des méthodes de refactorisation de votre code. pour le rendre plus testable.


Comment calculez-vous ce pourcentage de couverture? Qu'est-ce que cela signifie de couvrir 90% de votre code de toute façon?
Samedi

2
@zneak: il existe des outils de couverture de code qui les calculent pour vous. Un rapide Google pour "couverture de code" devrait en faire apparaître un certain nombre. L'outil suit les lignes de code exécutées lors de l'exécution des tests et base le nombre total de lignes de code dans le ou les assemblage (s) pour obtenir le pourcentage de couverture.
Steven Evers le

-1. Ne répond pas à la question:The problem is, I don't know _what_ to test
Alexander Bird

6

Si vous commencez à suivre les pratiques de développement piloté par les tests , elles vous guideront tout au long du processus et sauront naturellement quoi tester. Quelques endroits pour commencer:

Les tests viennent en premier

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

Écrire des tests de régression

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

Red-Green-Refactor-Repeat

Rouge : commencez par écrire un test élémentaire pour le comportement que vous essayez d'implémenter. Pensez à cette étape au moment d’écrire un exemple de code qui utilise la classe ou la fonction sur laquelle vous travaillez. Assurez-vous qu'il compile / n'a pas d'erreur de syntaxe et qu'il échoue . Cela devrait être évident: vous n'avez écrit aucun code, il doit donc échouer, n'est-ce pas? La chose importante à apprendre ici est que, à moins que le test échoue au moins une fois, vous ne pouvez jamais être sûr que s'il réussit, il le fait à cause de quelque chose que vous avez commis pour une raison fictive.

Vert : écrivez le code le plus simple et le plus stupide qui fait passer le test. N'essayez pas d'être intelligent. Même si vous voyez qu'il y a un cas évident de bord, mais que le test en tient compte, n'écrivez pas de code pour le gérer (mais n'oubliez pas le cas de bord: vous en aurez besoin plus tard). L'idée est que chaque élément de code que vous écrivez, chaque élément if, try: ... except: ...devrait être justifié par un scénario 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 noms de méthodes correctement. Voir si le test est toujours en train de passer. Optimiser. Exécutez le test à nouveau.

Répétez : vous vous souvenez du cas limite que le test n'a pas couvert, non? Alors, maintenant c'est son grand moment. Ecrivez un cas de test qui couvre cette situation, regardez-le échouer, écrivez du code, voyez-le passer, refactor.

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 devriez pas tester les fonctions de la bibliothèque, la bibliothèque standard ou votre compilateur. Aussi, essayez d'éviter de tester le "monde". Cela inclut: l’appel d’API Web externes, l’utilisation de bases de données intensives, etc. Chaque fois que vous pouvez essayer de vous en moquer (créez un objet qui suit la même interface, mais renvoie des données statiques prédéfinies).


1
En supposant que j'ai déjà une base de code existante et (autant que je puisse voir) opérationnelle, que dois-je faire?
zneak

Cela peut être un peu plus difficile (selon la façon dont le code est écrit). Commencez par des tests de régression (ils ont toujours un sens), puis essayez d’écrire des tests unitaires pour vous prouver que vous comprenez bien ce que fait le code. Il est facile de se sentir dépassé par la quantité de travail à faire (apparemment), mais: certains tests sont toujours meilleurs que l'absence de tests du tout.
Ryszard Szopa

3
-1 Je ne pense pas que ce soit une très bonne réponse à cette question . La question ne porte pas sur le TDD, mais sur ce qu'il faut tester lors de l'écriture de tests unitaires. Je pense qu'une bonne réponse à la question devrait s'appliquer à une méthodologie non-TDD.
Bryan Oakley

1
Si vous le touchez, testez-le. Et Clean Code (Robert C Martin) vous suggère d’écrire des "tests d’apprentissage" pour du code tiers. De cette façon, vous apprendrez à l'utiliser et vous aurez des tests dans le cas où une nouvelle version modifierait le comportement que vous utilisez.
Roger Willcocks

3

Pour les tests unitaires, commencez par vérifier que le logiciel répond aux objectifs pour lesquels il a été conçu. Ce devrait être le tout premier cas que vous écrivez. Si une partie de la conception est "elle devrait renvoyer une exception si vous passez en courrier indésirable", testez-la aussi car elle fait partie de la conception.

Commence avec ça. À mesure que vous maîtriserez les tests les plus élémentaires, vous commencerez à savoir si cela est suffisant ou non, et vous verrez d'autres aspects de votre code nécessitant des tests.


0

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

Quoi de trop simple pour casser? Champs de données, accesseurs de propriétés mortes cérébrales et frais généraux similaires. Tout le reste met probablement en œuvre une partie identifiable d'une exigence et peut bénéficier d'un test.

Bien entendu, votre kilométrage et les pratiques de votre environnement de travail peuvent varier.


D'accord. Alors, quels cas devrais-je tester? Le cas "normal"? L'affaire Edge?
Samedi

3
Règle de base? Un ou deux tout au long du chemin doré, et juste à l'intérieur et juste en dehors de tout bord.
Jeffrey Hantin le

@JeffreyHantin C'est "l'analyse de la valeur limite" dans une réponse différente.
Roger Willcocks
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.