Comment tester les tests?


53

Nous testons notre code pour le rendre plus correct (en fait, moins susceptible d'être incorrect ). Cependant, les tests sont aussi du code - ils peuvent également contenir des erreurs. Et si vos tests sont bogués, ils n'améliorent guère votre code.

Je peux penser à trois types d’erreurs possibles dans les tests:

  1. Erreurs logiques, lorsque le programmeur a mal compris la tâche à accomplir et que les tests font ce qu'il pensait devoir faire, ce qui est faux;

  2. Erreurs dans la structure de test sous-jacente (par exemple, une abstraction moqueuse qui fuit);

  3. Bugs dans les tests: le test est légèrement différent de ce que le programmeur pense.

Les erreurs de type (1) semblent impossibles à prévenir (à moins que le programmeur ... devienne plus intelligent). Cependant, (2) et (3) peuvent être traitables. Comment gérez-vous ces types d'erreurs? Avez-vous des stratégies spéciales pour les éviter? Par exemple, écrivez-vous des tests "vides" spéciaux, qui vérifient uniquement les présupposés de l'auteur du test? De plus, comment abordez-vous le débogage d'un scénario de test cassé?


3
Chaque article d'introduction que j'ai lu sur les moqueries semble poser problème. Une fois que vous commencez à vous moquer, les tests semblent toujours plus compliqués que le code qu'ils testent. Évidemment, cela est moins probable lorsque vous testez du code du monde réel, mais c'est assez décourageant lorsque vous essayez d'apprendre.
Carson63000

@ Carson63000 S'il s'agit d'un simple test, testez quelque chose avec une maquette testée , la complexité est partagée et sous contrôle (, je pense).
mlvljr

13
Mais alors comment testez-vous les tests?
octobre

+1 L'élément 1 pourrait être une erreur d'exigences. Ne peut être évité qu'en révisant les exigences. Probablement hors des mains du programmeur, à moins qu'il ne soit également l'analyste des exigences
MarkJ le

@ocodo: De la même manière que vous surveillez les observateurs. :)
Greg Burghardt

Réponses:


18

Les tests sont déjà testés. De par leur conception, les tests sont protégés contre les bogues, car ils ne détectent que les différences entre le code et nos attentes. S'il y a des problèmes, nous avons une erreur. L'erreur pourrait être dans le code ou avec la même probabilité dans les tests.

  1. Certaines techniques vous empêchent d'ajouter le même bogue dans votre code et vos tests:

    1. Le client doit être une personne différente de celle du responsable de la mise en œuvre.
    2. Commencez par écrire les tests, puis le code (comme dans Test Driven Development).
  2. Vous n'avez pas besoin de tester la plateforme sous-jacente. Les tests non seulement exercent le code que vous avez écrit, mais ils le font aussi depuis la plate-forme. Bien que vous ne vouliez pas nécessairement attraper des bogues dans la plate-forme de test, il est très difficile d'écrire du code et des tests qui cachent toujours un bogue dans la plate-forme. En d'autres termes, il est très difficile d'avoir un bogue systématique dans vos tests / code et dans la plate-forme, et la probabilité est réduite à chaque test que vous créez. Même si vous essayiez de faire cela, vous auriez une tâche très difficile.

  3. Vous pourriez avoir des bugs lors des tests mais en général, ils sont facilement détectés car les tests sont testés avec le code développé. Entre le code et les tests, vous avez un retour d’auto-application. Les deux prédisent comment un appel spécifique d'une interface devrait se comporter. Si la réponse est différente, vous n'avez pas nécessairement de bogue dans le code. Vous pourriez aussi avoir un bug dans le test.


Très belle réponse. J'aime l'idée d'une boucle d'auto-renforcement entre les tests et le code et l'observation selon laquelle il serait difficile d'écrire des tests en masquant systématiquement des bogues dans la plateforme sous-jacente.
Ryszard Szopa

ce qui n'empêche pas la création de tests basés sur des suppositions erronées sur ce que le code réel devrait faire. Ce qui peut conduire à de très vilains bugs qui ne sont pas détectés lors des tests. Le seul moyen d'éviter cela est de faire écrire les tests par une tierce partie, sans aucun lien avec l'organisation qui écrit le code lui-même, afin qu'elle ne puisse pas "polluer" la pensée de chacun lorsqu'il s'agit d'interpréter les exigences.
Jwenting

24

Essayez de faire les tests individuels aussi courts que possible.

Cela devrait réduire les chances de créer un bogue en premier lieu. Même si vous parvenez à en créer un, il est plus facile à trouver. Les tests unitaires sont supposés être petits et spécifiques, avec une faible tolérance à l'échec et à la déviation.

En fin de compte, c'est probablement juste une question d'expérience. Plus vous écrivez de tests, plus vous y réussissez, moins vous avez de chances de faire des tests de mauvaise qualité.


3
Et si les tests nécessitaient une configuration plutôt compliquée? Parfois, ce genre de choses n'est pas sous votre contrôle.
Ryszard Szopa

Eh bien, je suppose que la configuration compliquée est la "condition initiale" des tests. Si cela échoue, tous vos tests devraient échouer. En fait, je travaille actuellement sur un projet de ce type, et les personnes qui n’utilisaient jamais de tests unitaires demandaient constamment la même chose ... jusqu’à ce que nous leur expliquions réellement ce qu’ils sont réellement :) Ensuite, ils se sont rendu compte que cela pouvait être fait complexité du projet.
dr Hannibal Lecter

Le meilleur moyen de vérifier que cette "condition initiale" est remplie est précisément le but de ma question. Est-ce que vous écrivez un test séparé pour cela? Ou simplement supposer que les tests échoueront si cette condition n'est pas vraie? Qu'en est-il de la situation lorsque la configuration n'est pas "catastrophique", juste légèrement en panne?
Ryszard Szopa

2
Vos tests devraient échouer si les conditions initiales ne sont pas correctes, voilà le problème. En état A, vous attendez le résultat B. Si vous n'avez pas d'état A, un test devrait échouer. À ce stade, vous pouvez rechercher les raisons de votre échec, de mauvaises conditions initiales ou d'un mauvais test, mais cela devrait échouer dans les deux cas. Même si c'est, comme tu le dis, "légèrement en retrait" (c'est "A" => "B"-à- dire "a" => "b", mais jamais "a" => "B"ou si ton test est mauvais).
Dr. Hannibal Lecter

19

Une tactique consiste à écrire le test avant le code qu'il teste et à s'assurer que le test échoue d'abord pour la bonne raison. Si vous utilisez TDD, vous devriez obtenir au moins ce niveau de test.

Un moyen plus exhaustif de tester la qualité d'une suite de tests consiste à utiliser le test de mutation .


2
Et que votre test échoue pour la bonne raison .
Frank Shearar

@ Frank - Oui. Je vais ajouter ça à la réponse.
Don Roby

Et vous ajoutez un nouveau test pour le nouveau comportement à tester. Ne pas ajouter aux tests existants.
Huperniketes

@DonRoby, Avez-vous trouvé le test de mutation utile dans la pratique? Quelles lacunes avez-vous trouvées dans vos cas de test avec cela?
jeudi

4

Pour les tests n ° 1 et n ° 3: les tests unitaires ne doivent contenir aucune logique. Dans ce cas, vous testez probablement plus d'une chose dans votre test unitaire. Une des meilleures pratiques pour les tests unitaires consiste à n’avoir qu’un seul test par test unitaire.

Regardez cette vidéo de Roy Osherove pour en savoir plus sur la manière de bien écrire les tests unitaires.


Annonce n ° 3 - Je conviens que les tests doivent être aussi simples que possible et ne contenir aucune logique. Cependant, pensez à la phase de configuration du test, lorsque vous créez les objets dont il aura besoin. Vous pouvez créer des objets légèrement faux. C'est le genre de problèmes auquel je pense.
Ryszard Szopa

Lorsque vous dites «objets légèrement incorrects», voulez-vous dire que l'état de l'objet n'est pas correct ou que sa conception n'est pas correcte? Pour l'état d'objet, vous pouvez probablement écrire des tests pour vérifier sa validité. Si la conception est erronée, le test doit échouer.
Piers Myers

3

En termes de n ° 1 - Je pense que c'est une bonne idée de réviser les paires / codes pour ce côté des choses. Il est facile de faire des présupposés ou de tout simplement se tromper, mais si vous devez expliquer en quoi consiste votre test, vous êtes plus susceptible de réagir si vous visez la mauvaise cible.


2

Il doit y avoir un moment où l’on devrait arrêter d’essayer de faire des tests unitaires. Devrait savoir quand tracer la ligne. Devrions-nous écrire des cas de test pour tester des cas de test? Qu'en est-il des nouveaux cas de test écrits pour tester des cas de test? Comment allons-nous les tester?

if (0 > printf("Hello, world\n")) {
  printf("Printing \"Hello, world\" failed\n");
}

Edit: mis à jour avec l'explication suggérée par le commentaire.


-1 Quoi? Cela semble n'avoir aucune pertinence.
alternative

2
Il doit y avoir un moment où l’on devrait arrêter d’essayer de faire des tests unitaires. Devrait savoir quand tracer la ligne. Devrions-nous écrire des cas de test pour tester des cas de test? Qu'en est-il des nouveaux cas de test écrits pour tester des cas de test? Comment allons-nous les tester?
père

2
Traitez Brain soulevé EInfiniteRecursion en tentant d'extrapoler votre déclaration ...
Mason Wheeler

Remplacez votre réponse avec votre commentaire et vous obtiendrez un bonus de +1
Note à moi - penser à un nom

3
En toute justice, votre exemple est un homme de paille. Vous testez le sous-système printf () dans une bibliothèque C, et non le programme appelant printf (). Je conviens toutefois qu’à un moment donné, il faut rompre le test récursif des tests.
Tim Post

2

Hey.
Vous devez faire des applications:

  • Ton produit
  • Votre test pour ce produit.

Lorsque vous exécutez des tests sur votre produit, vous n'êtes pas intéressé par le test lui-même, mais par l' interaction entre votre produit et vos tests. Si le test échoue, cela ne signifie pas que l'application a un bogue. Il indique que l' interaction entre le produit et le test n'a pas abouti . Il vous appartient maintenant de déterminer ce qui a mal tourné. Cela peut être soit:

  • l'application ne se comporte pas comme prévu (cette attente est exprimée dans votre test)
  • l'application se comporte correctement, vous n'avez tout simplement pas documenté ce comportement correctement (dans vos tests)

Pour moi, les tests qui échouent ne sont pas un simple retour d'information, mais ceci est faux . C'est un indicateur d'incohérence, et je dois examiner les deux pour vérifier si le besoin s'est mal passé. En fin de compte, je suis responsable de la vérification de l’application, les tests ne sont qu’un outil pour mettre en évidence les domaines qui méritent d’être vérifiés.

Les tests ne vérifient que certaines parties de l'application. Je teste l'application, je teste les tests.


2

Les tests ne doivent pas être assez "intelligents" pour héberger des bugs.

Le code que vous écrivez implémente un ensemble de spécifications. (Si X, alors Y, sauf Z, auquel cas Q, etc., etc.). Tout ce que l’essai devrait faire est de déterminer que X est vraiment Y, sauf si Z est dans quel cas. Cela signifie que tout ce qu’un test devrait faire est de définir X et de vérifier Y.

Mais cela ne couvre pas tous les cas, vous dites probablement, et vous auriez raison. Mais si vous faites le test suffisamment "intelligent" pour savoir que X ne doit être sélectionné que par Y, sinon par Z, vous réimpliquez la logique métier dans le test. C'est problématique pour les raisons que nous allons approfondir un peu plus loin. Vous ne devriez pas améliorer la couverture de code en rendant votre premier test "plus intelligent", mais plutôt en ajoutant un second test muet qui définit X et Z et vérifie Q. Ainsi, vous disposerez de deux tests, l'un couvrant le cas général ( parfois aussi connu comme le chemin heureux) et celui qui couvre le cas limite comme un test séparé.

Il y a plusieurs raisons à cela. Premièrement, comment déterminer si un test ayant échoué est dû à un bogue dans la logique métier ou à un bogue dans les tests? Évidemment, la réponse est que si les tests sont aussi simples que possible, il est très peu probable qu’ils hébergent des insectes. Si vous pensez que vos tests doivent être testés, vous testez mal .

D'autres raisons incluent le fait que vous ne faites que reproduire vos efforts (comme je l'ai déjà mentionné, écrire un test suffisamment intelligent pour exploiter toutes les possibilités dans un seul test revient essentiellement à reproduire la logique métier que vous essayez de tester en premier lieu), si les exigences changent. alors les tests devraient être faciles à modifier pour refléter les nouvelles exigences, les tests servent de sorte de documentation (ils constituent un moyen formel de dire quelle est la spécification de l'unité testée), etc.

TL: DR: Si vos tests doivent être testés, vous le faites mal. Ecrire des tests stupides .


0

Pas une réponse (je n'ai pas le privilège de commenter), mais je me demandais si vous aviez oublié d'autres raisons de développer des cas de test ...
Une fois que vous avez trouvé tous les bugs dans les tests, vous pouvez facilement tester votre application par régression. Les suites de tests automatisées vous aideraient à détecter les problèmes plus tôt, avant l'intégration. Les modifications apportées aux exigences sont relativement plus faciles à tester, car elles peuvent devenir plus récentes, une version modifiée d'anciens cas de test ayant passé avec succès, et les cas plus anciens restant destinés à détecter les échecs.


0

Réponse courte: le code de production teste les tests .

Comparez cela au modèle de crédit / débit utilisé en économie. Les mécanismes sont très simples - Si le crédit diffère du débit, il y a un problème.

Il en va de même pour les tests unitaires - Si un test échoue, cela signifie que quelque chose ne va pas. Il s’agit peut-être du code de production, mais également du code de test! Cette dernière partie est importante.

Notez que vos bogues de type (1) ne peuvent pas être trouvés par les tests unitaires. Pour éviter ce type de bogues, vous avez besoin d'autres outils.

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.