Pourquoi les tests unitaires échouent-ils si mal?

93

Dans certaines organisations, le processus de publication du logiciel consiste apparemment, semble-t-il, à utiliser les tests unitaires, mais à tout moment, tous les tests unitaires doivent réussir. Par exemple, il pourrait y avoir un écran qui montre que tous les tests unitaires passent au vert - ce qui est supposé être bon.

Personnellement, je ne pense pas que ce soit comme cela pour les raisons suivantes:

  1. Cela promeut l'idée que le code devrait être parfait et qu'aucun bogue n'existerait - ce qui dans le monde réel est sûrement impossible pour un programme de toute taille.

  2. Il est décourageant d’imaginer des tests unitaires qui échoueront. Ou bien, proposez des tests unitaires difficiles à résoudre.

  3. Si, à un moment donné, tous les tests unitaires réussissent, il n’ya pas de tableau général de l’état du logiciel à un moment donné. Il n'y a pas de feuille de route / objectif.

  4. Cela dissuade d'écrire les tests unitaires en amont, avant la mise en œuvre.

Je dirais même que même la publication de logiciels avec des tests unitaires défaillants n’est pas forcément mauvaise. Au moins, vous savez alors que certains aspects du logiciel ont des limites.

Est-ce que j'ai râté quelque chose? Pourquoi les organisations s'attendent-elles à ce que tous les tests unitaires réussissent? N'est-ce pas vivre dans un monde de rêve? Et cela ne décourage-t-il pas réellement une compréhension réelle du code?

utilisateur619818
la source
Les commentaires ne sont pas pour une discussion prolongée; cette conversation a été déplacée pour discuter .
maple_shaft

Réponses:

270

Cette question contient plusieurs idées fausses à mon humble avis, mais la principale sur laquelle je voudrais insister est qu'elle ne fait pas de distinction entre les branches de développement local, le tronc, les stades de déploiement ou de libération.

Dans une branche de développement locale, il est probable que des tests unitaires échouent à tout moment. Dans le coffre, cela n’est acceptable que dans une certaine mesure, mais c’est déjà un bon indicateur pour régler le problème au plus vite. Notez que les tests unitaires en échec dans le coffre peuvent gêner le reste de l'équipe, car ils exigent que tout le monde vérifie si sa dernière modification a été à l'origine de l'échec.

Dans une branche de mise en attente ou de version, les tests qui échouent constituent une "alerte rouge", ce qui montre qu'il y a eu un problème avec un ensemble de modifications, lorsqu'il a été fusionné à partir du tronc dans la branche de publication.

Je dirais même que même la publication de logiciels avec des tests unitaires défaillants n’est pas forcément mauvaise.

La publication de logiciels contenant des bogues connus inférieurs à une certaine gravité n’est pas nécessairement une mauvaise chose . Cependant, ces problèmes connus ne devraient pas provoquer l'échec du test unitaire. Sinon, après chaque test unitaire, il faudra examiner les 20 tests unitaires échoués et vérifier un par un si l'échec est acceptable ou non. Cela devient fastidieux, source d'erreurs et élimine une grande partie de l'aspect automatisation des tests unitaires.

Si vous avez vraiment des tests pour les bogues connus acceptables, utilisez la fonction de désactivation / ignorance de votre outil de test unitaire (afin qu'ils ne soient pas exécutés par défaut, uniquement à la demande). De plus, ajoutez un ticket de priorité basse à votre outil de suivi des problèmes pour ne pas oublier le problème.

Doc Brown
la source
18
Je pense que c'est la vraie réponse. OP mentionne "processus de libération" et "un écran [montrant les résultats du test]", qui ressemble à un serveur de build. La sortie n'est pas la même chose que le développement (ne pas développer en production!); c'est bien d'avoir des tests qui échouent dans dev, ce sont des TODOs; ils doivent tous être verts (DONE) lorsqu'ils sont poussés vers le serveur de construction.
Warbo
7
Une réponse bien meilleure que la plus votée. Il montre une compréhension de la provenance de l'opération sans leur expliquer la situation idéale dans le monde, reconnaît la possibilité de bugs connus (pour lesquels la feuille de route n'est pas supprimée dans son intégralité pour résoudre un cas rare) et explique que les tests unitaires ne devraient définitivement être vert dans une branche / processus de publication.
Sebastiaan van den Broek
5
@SebastiaanvandenBroek: merci pour votre réponse positive. Soyons clairs: les tests unitaires qui échouent à mon humble avis devraient être rares, même dans le coffre, car obtenir trop souvent de tels échecs gênera l’ensemble de l’équipe, pas seulement celle qui a effectué le changement qui a provoqué l’échec.
Doc Brown
4
Je pense que le problème ici est de penser que tous les tests automatisés sont des tests unitaires. De nombreux frameworks de test incluent la possibilité de marquer les tests qui doivent échouer (souvent appelé XFAIL). (Cela diffère d'un test qui nécessite un résultat d'erreur. Les tests XFAIL réussiraient idéalement, mais ils ne le feraient pas.) La suite de tests réussit toujours avec ces échecs. Le cas d'utilisation le plus courant concerne les éléments qui n'échouent que sur certaines plates-formes (et XFAIL uniquement sur celles-ci), mais l'utilisation de la fonctionnalité pour suivre quelque chose qui demandera trop de travail à réparer pour le moment est également raisonnable. Mais ces types de tests ne sont généralement pas des tests unitaires.
Kevin Cathcart
1
+1, bien que je suggère un léger ajout (en gras) à cette phrase: "Cela devient fastidieux, source d'erreurs, incite les gens à ignorer les échecs de la suite de tests comme du bruit , et rejette une énorme partie de l'aspect automatisation des tests unitaires .
mtraceur
228

... Tous les tests unitaires passent au vert - ce qui est censé être bon.

Il est bon. Non "supposé être" à ce sujet.

Cela promeut l'idée que le code devrait être parfait et qu'aucun bogue n'existerait - ce qui dans le monde réel est sûrement impossible pour un programme de toute taille.

Non. Cela prouve que vous avez testé le code aussi bien que vous le pouviez jusqu'à présent. Il est tout à fait possible que vos tests ne couvrent pas tous les cas. Si tel est le cas, les erreurs éventuellement générées apparaîtront dans les rapports de bogue et vous écrirez des tests [qui échoueront] pour reproduire les problèmes, puis vous corrigerez l'application pour que les tests réussissent.

Il est décourageant d’imaginer des tests unitaires qui échoueront.

Des tests négatifs ou négatifs fixent des limites strictes à ce que votre application acceptera et n'acceptera pas. La plupart des programmes que je connais s'opposeront à une "date" du 30 février. De plus, les développeurs, les créatifs que nous sommes, ne veulent pas casser "leurs bébés". La focalisation qui en résulte sur les cas de "voie heureuse" conduit à des applications fragiles qui se cassent souvent.

Pour comparer l'état d'esprit du développeur et du testeur:

  • Un développeur s'arrête dès que le code fait ce qu'il veut.
  • Un testeur s'arrête lorsqu'il ne peut plus faire casser le code.

Ces perspectives sont radicalement différentes et il est difficile pour de nombreux développeurs de concilier.

Ou bien, proposez des tests unitaires difficiles à résoudre.

Vous n'écrivez pas de tests pour vous faire travailler. Vous écrivez des tests pour vous assurer que votre code fait ce qu'il est censé faire et, plus important encore, qu'il continue de faire ce qu'il est censé faire après que vous ayez modifié son implémentation interne.

  • Le débogage "prouve" que le code fait ce que vous voulez aujourd'hui .
  • Les tests "prouvent" que le code fait toujours ce que vous voulez au fil du temps .

Si, à un moment donné, tous les tests unitaires réussissent, il n’ya pas de tableau général de l’état du logiciel à un moment donné. Il n'y a pas de feuille de route / objectif.

Le seul test "d'image" que vous obtenez est un instantané indiquant que le code "fonctionne" au moment où il a été testé. Comment cela évolue après cela est une autre histoire.

Cela dissuade d'écrire les tests unitaires en amont, avant la mise en œuvre.

C'est exactement ce que vous devriez faire. Ecrivez un test qui échoue (car la méthode à tester n'a pas encore été implémentée), puis écrivez le code de la méthode pour que celle-ci fonctionne et, par conséquent, que le test réussisse. C'est à peu près le noeud du développement piloté par les tests.

Je dirais même que même la publication de logiciels avec des tests unitaires défaillants n’est pas forcément mauvaise. Au moins, vous savez alors que certains aspects du logiciel ont des limites.

La publication de code avec des tests interrompus signifie qu'une partie de ses fonctionnalités ne fonctionne plus comme auparavant. Cela peut être un acte délibéré parce que vous avez corrigé un bogue ou amélioré une fonctionnalité (mais vous auriez dû d'abord modifier le test pour qu'il échoue, puis coder le correctif / l'amélioration, pour que le test fonctionne dans le processus). Plus important encore: nous sommes tous humains et nous faisons des erreurs. Si vous enfreignez le code, vous devez alors interrompre les tests et ces derniers doivent déclencher des sonnettes d'alarme.

N'est-ce pas vivre dans un monde de rêve?

Si quoi que ce soit, il vit dans le monde réel , en reconnaissant que les développeurs ne sont ni omniscient ni infallable, que nous ne faire des erreurs et que nous avons besoin d' un filet de sécurité pour nous attraper si et quand nous le faisons en désordre!
Entrez les tests.

Et cela ne décourage-t-il pas réellement une compréhension réelle du code?

Peut-être. Vous n'avez pas nécessairement besoin de comprendre l'implémentation de quelque chose pour écrire des tests (ça en fait partie). Les tests définissent le comportement et les limites de l'application et garantissent que celles-ci restent identiques sauf si vous les modifiez délibérément.

Phill W.
la source
7
@Tibos: Désactiver un test revient à commenter une fonction. Vous avez le contrôle de version. Utilise le.
Kevin
6
@ Kevin, je ne sais pas ce que vous entendez par «l'utiliser». Je marque un test comme "ignoré" ou "en attente" ou toute convention que mon exécuteur de tests utilise, et je valide cette balise de saut dans le contrôle de version.
départ
4
@dcorking: Je veux dire, ne commentez pas le code, supprimez-le. Si vous décidez par la suite que vous en avez besoin, restaurez-le à partir du contrôle de version. Commettre un test désactivé n'est pas différent.
Kevin
4
"Il est tout à fait possible que vos tests ne couvrent pas tous les cas." J'irais jusqu'à dire que pour chaque élément de code non trivial testé, il est clair que tous les cas ne sont pas couverts.
CorsiKa
6
@Tibos Les partisans des tests unitaires disent que le temps de cycle entre l'écriture d'un test ayant échoué et l'écriture du code doit être court (par exemple, 20 minutes. Certains revendiquent 30 secondes). Si vous n'avez pas le temps d'écrire le code immédiatement, c'est probablement trop complexe. Si ce n'est pas complexe, supprimez le test car il peut être récrit si la fonctionnalité supprimée est ajoutée à nouveau. Pourquoi ne pas commenter? Vous ne savez pas que la fonctionnalité sera de nouveau ajoutée, le test (ou le code) mis en commentaire est simplement du bruit.
CJ Dennis
32

Pourquoi les tests unitaires échouent-ils si mal?

Ils ne le sont pas - le développement piloté par les tests repose sur la notion de tests ayant échoué. Échec des tests unitaires pour stimuler le développement, échec des tests d'acceptation pour générer une histoire ....

Ce qui vous manque, c’est le contexte ; Où les tests unitaires sont-ils autorisés à échouer?

La réponse habituelle est que les tests unitaires sont autorisés à échouer uniquement dans les sandbox privés.

La notion de base est la suivante: dans un environnement où les tests qui échouent sont partagés, il faut faire un effort supplémentaire pour comprendre si une modification du code de production a introduit une nouvelle erreur. La différence entre zéro et non zéro est beaucoup plus facile à détecter et à gérer que la différence entre N et pas N.

De plus, garder le code partagé propre signifie que les développeurs peuvent rester sur leurs tâches. Lorsque je fusionne votre code, je n'ai pas besoin de changer de contexte pour résoudre le problème que je suis payé, mais plutôt pour calibrer ma compréhension du nombre de tests qui devraient échouer. Si le code partagé réussit tous les tests, les échecs apparaissant lors de la fusion de mes modifications doivent faire partie de l'interaction entre mon code et la ligne de base vierge existante.

De même, lors de l’accueil, un nouveau développeur peut devenir productif plus rapidement, car il n’a pas besoin de perdre du temps à découvrir quels tests sont «acceptables».

Pour être plus précis: la discipline est que les tests qui courent pendant la construction doivent réussir.

Comme je peux le dire, il n’ya rien de mal à échouer les tests qui sont désactivés .

Par exemple, dans un environnement "d'intégration continue", vous partagerez le code sur une cadence élevée. Une intégration souvent ne signifie pas nécessairement que vos modifications doivent être prêtes à être publiées. Il existe toute une gamme de techniques de déploiement dans l'obscurité qui empêchent le trafic d'être libéré dans des sections du code tant qu'elles ne sont pas prêtes.

Ces mêmes techniques peuvent également être utilisées pour désactiver les tests ayant échoué.

L'un des exercices sur lesquels j'ai participé au moment de la publication concernait le développement d'un produit comportant de nombreux tests infructueux. La réponse que nous avons proposée consistait simplement à parcourir la suite, en désactivant les tests ayant échoué et en documentant chacun. Cela nous a permis d'atteindre rapidement le point où tous les tests activés passaient et que la direction / le donneur d'objectifs / le propriétaire d'or pouvait tous voir les transactions que nous avions effectuées pour y parvenir et prendre des décisions éclairées concernant le nettoyage par rapport aux nouveaux travaux.

En bref: il existe d’autres techniques de suivi du travail non effectuées que de laisser un tas de tests qui échouent dans la suite en cours d’exécution.

VoiceOfUnreason
la source
J'aurais dit "Il n'y a rien de mal à échouer les tests qui sont désactivés ".
CJ Dennis
Ce changement clarifie certainement le sens. Je vous remercie.
VoiceOfUnreason
26

Il y a beaucoup de bonnes réponses, mais j'aimerais ajouter un autre angle qui, à mon avis, n'est pas encore bien couvert: quel est l'intérêt de faire des tests?

Les tests unitaires ne sont pas là pour vérifier que votre code est exempt de bogues.

Je pense que c'est la principale idée fausse. Si tel était leur rôle, vous vous attendez à ce que les tests échouent un peu partout. Mais plutôt,

Les tests unitaires vérifient que votre code fait ce que vous pensez.

Dans des cas extrêmes, il peut être nécessaire de vérifier que les bogues connus ne sont pas corrigés. Le but est de contrôler votre base de code et d’éviter les modifications accidentelles. Lorsque vous apportez une modification, tout va bien et on s'attend en fait à ce que certains tests échouent: vous modifiez le comportement du code. Le test fraîchement cassé est maintenant une belle trace de ce que vous avez changé. Vérifiez que toutes les ruptures sont conformes à ce que vous voulez de votre changement. Si oui, il suffit de mettre à jour les tests et de continuer. Sinon, votre nouveau code est définitivement bogué, corrigez-le avant de le soumettre!

Maintenant, tout ce qui précède ne fonctionne que si tous les tests sont verts, ce qui donne un résultat fortement positif: c’est exactement comme cela que le code fonctionne. Les tests rouges n'ont pas cette propriété. "C’est ce que ce code ne fait pas" est rarement une information utile.

Les tests d'acceptation peuvent être ce que vous recherchez.

Il existe des tests d'acceptation. Vous pouvez écrire un ensemble de tests qui doivent être remplis pour appeler le prochain jalon. Ce sont ok pour être rouge, parce que c'est ce pour quoi ils ont été conçus. Mais ils sont très différents des tests unitaires et ne peuvent ni ne doivent les remplacer.

Frax
la source
2
Une fois, j'ai dû remplacer une bibliothèque par une autre. Les tests unitaires m'ont aidé à garantir que tous les cas critiques étaient toujours traités de manière identique par le nouveau code.
Thorbjørn Ravn Andersen
24

Je le considère comme l’équivalent logiciel du syndrome de la fenêtre cassée .

Les tests de fonctionnement me disent que le code est d'une qualité donnée et que les propriétaires du code en tiennent compte.

Pour ce qui est du moment où vous devez vous soucier de la qualité, cela dépend de la branche / du référentiel de code source sur lequel vous travaillez. Le code de développement peut très bien avoir des tests cassés indiquant un travail en cours (si tout va bien!).

Les tests interrompus sur une branche / un référentiel pour un système actif doivent immédiatement déclencher des sonneries d’alarme. Si les tests interrompus sont autorisés à continuer à échouer ou s'ils sont marqués de manière permanente par "ignorer", attendez-vous à ce que leur nombre augmente progressivement. Si ceux-ci ne sont pas régulièrement examinés, le précédent aura été établi qu'il est acceptable de laisser des tests interrompus.

Les tests rompus sont considérés de manière péjorative dans de nombreux magasins au point d’avoir une restriction sur la possibilité de commettre un code erroné .

Robbie Dee
la source
9
Si les tests documentent la façon dont un système est, ils devraient certainement toujours être réussis - s'ils ne le sont pas, cela signifie que les invariants sont cassés. Mais s'ils documentent la manière dont un système est supposé être, les tests qui échouent peuvent également être utilisés - tant que votre infrastructure de test unitaire prend en charge un bon moyen de les marquer comme "problèmes connus", et si vous les liez avec un élément dans votre suivi des problèmes. Je pense que les deux approches ont leur mérite.
Luaan
1
@ Luan Oui, cela suppose plutôt que tous les tests unitaires sont créés de manière égale. Il n’est certainement pas rare que les responsables de la compilation découpent les tests en fonction d’un attribut en fonction de leur durée d’exécution, de leur fragilité et de divers autres critères.
Robbie Dee
Cette réponse est excellente par mon expérience personnelle. Une fois que certaines personnes se sont habituées à ignorer un ensemble de tests ayant échoué ou à enfreindre les meilleures pratiques, laissez quelques mois s'écouler et vous verrez que% des tests ignorés augmentent de façon spectaculaire, la qualité du code tombant au niveau "script de piratage" . Et il sera très difficile de rappeler tout le monde au processus.
usr-local-ΕΨΗΕΛΩΝ
11

Voici l'erreur logique sous-jacente:

Si c'est bon quand tous les tests sont réussis, alors ça doit être mauvais si les tests échouent.

Avec les tests unitaires, C’EST bon lorsque tous les tests sont réussis. Il est également bon quand un test échoue. Les deux ne doivent pas être dans l'opposition.

Un test qui échoue est un problème qui a été détecté par votre outillage avant qu'il n'atteigne un utilisateur. C'est une occasion de réparer une erreur avant sa publication. Et c'est une bonne chose.

Joel Coehoorn
la source
Ligne de pensée intéressante. Je vois que l'erreur de la question est plutôt la suivante: "comme c'est bon quand un test unitaire échoue, c'est mauvais quand tous les tests réussissent".
Doc Brown
Bien que votre dernier paragraphe soit un bon point, il semble que le problème soit davantage une incompréhension de "à tout moment, tous les tests unitaires doivent réussir" (comme l'indique la réponse acceptée) et du point de contrôle des tests unitaires.
Dukeling
9

La réponse de Phill W est excellente. Je ne peux pas le remplacer.

Cependant, je souhaite mettre l’accent sur un autre aspect de la confusion.

Dans certaines organisations, le processus de publication du logiciel consiste apparemment, semble-t-il, à utiliser des tests unitaires, mais à tout moment, tous les tests unitaires doivent réussir.

"à tout moment" surestime votre cas. Ce qui est important, c'est que les tests unitaires réussissent après la mise en œuvre d' un certain changement, avant de commencer à implémenter un autre changement.
C’est ainsi que vous gardez une trace du changement à l’origine d’un bogue. Si les tests unitaires ont commencé à échouer après avoir implémenté le changement 25 mais avant d' implémenter le changement 26, alors vous savez que le changement 25 a provoqué le bogue.

Lors de la mise en œuvre d'une modification, les tests unitaires peuvent bien sûr échouer. Cela dépend beaucoup de la taille du changement. Si je suis en train de réaménager une fonctionnalité principale, ce qui est plus qu'un ajustement mineur, je vais probablement interrompre les tests un moment, jusqu'à ce que je finisse de mettre en œuvre ma nouvelle version de la logique.


Cela peut créer des conflits quant aux règles de l'équipe. J'ai effectivement rencontré cela il y a quelques semaines:

  • Chaque commit / push provoque une construction. La construction ne doit jamais échouer (si tel est le cas ou si l'un des tests échoue, le développeur commettant est tenu responsable).
  • Chaque développeur est censé appliquer ses modifications (même si elles sont incomplètes) en fin de journée afin que l'équipe responsable puisse procéder à la révision du code le matin.

Soit la règle serait bien. Mais les deux règles ne peuvent pas fonctionner ensemble. Si on m'assigne un changement majeur qui prend plusieurs jours, je ne serais pas en mesure de respecter les deux règles en même temps. À moins que je ne commente mes modifications tous les jours et que je ne les engage que lorsque tout est terminé; ce qui est juste un travail absurde.

Dans ce scénario, le problème ici n’est pas que les tests unitaires n’ont aucune utilité; c'est que la société a des attentes irréalistes . Leur ensemble de règles arbitraires ne couvre pas tous les cas, et le non-respect des règles est considéré aveuglément comme un échec du développeur plutôt que comme un manquement à la règle (ce qui est le cas dans mon cas).

Flater
la source
3
Cela peut notamment fonctionner en utilisant des branches, de telle sorte que les développeurs valident et poussent vers des branches de fonctionnalités qui n'ont pas besoin de générer proprement lorsqu'elles sont incomplètes, mais les validations dans la branche principale déclenchent une construction, qui doit être construite proprement.
Gwyn Evans
1
Appliquer des modifications incomplètes est absurde, je ne vois aucune justification pour le faire. Pourquoi ne pas revoir le code lorsque le changement est terminé?
Callum Bradbury
Tout d’abord, c’est un moyen rapide de s’assurer que le code ne se trouve pas uniquement sur l’ordinateur portable / de travail du développeur si le disque dur cesse de fonctionner ou est perdu de quelque manière que ce soit - s’il existe une politique d’engagement même en plein milieu du travail, alors il y a une quantité limitée de travail à risque.
Gwyn Evans
1
Les drapeaux de caractéristiques corrigent le paradoxe apparent.
RubberDuck
1
@Flater oui, pour retravailler la logique existante aussi.
RubberDuck
6

Si vous ne corrigez pas tous les tests unitaires, vous pouvez rapidement entrer dans un état où personne ne répare les tests interrompus.

  1. Est incorrect car les tests unitaires réussis ne montrent pas que le code est parfait

  2. Il est décourageant de proposer un code difficile à tester également, ce qui est bon du point de vue de la conception.

  3. La couverture de code peut y aider (bien que ce ne soit pas une panacée). De plus, les tests unitaires ne sont qu'un aspect des tests. Vous souhaitez également des tests d'intégration / d'acceptation.

jk.
la source
6

Pour ajouter quelques points aux réponses déjà bonnes ...

mais à tout moment, tous les tests unitaires doivent réussir

Cela montre un manque de compréhension d'un processus de publication. Un échec de test peut indiquer une fonctionnalité planifiée sous TDD qui n'est pas encore implémentée; ou cela peut indiquer un problème connu pour lequel une solution est prévue pour une version ultérieure; ou bien la direction a peut-être décidé que cela n’était pas assez important, car les clients ne le remarqueraient probablement pas. L’essentiel, c’est que la direction ait rendu un jugement sur l’échec.

Cela promeut l'idée que le code devrait être parfait et qu'aucun bogue n'existerait - ce qui dans le monde réel est sûrement impossible pour un programme de toute taille.

D'autres réponses ont couvert les limites des tests.

Je ne comprends pas pourquoi vous pensez que l'élimination des bugs est un inconvénient. Si vous ne voulez pas livrer le code que vous avez vérifié (au mieux de vos capacités), il fait ce qu'il est censé faire, pourquoi travaillez-vous même dans un logiciel?

Si, à un moment donné, tous les tests unitaires réussissent, il n’ya pas de tableau général de l’état du logiciel à un moment donné. Il n'y a pas de feuille de route / objectif.

Pourquoi faut-il une feuille de route?

Les tests unitaires vérifient initialement que la fonctionnalité fonctionne, puis (en tant que tests de régression) vérifient que vous n'avez rien cassé par inadvertance. Pour toutes les fonctionnalités avec des tests unitaires existants, il n'y a pas de feuille de route . Chaque fonctionnalité est connue pour fonctionner (dans les limites des tests). Si ce code est terminé, il n'a pas de feuille de route car il ne nécessite aucun travail supplémentaire.

En tant qu’ingénieurs professionnels, nous devons éviter le piège du placage d’or. Les amateurs peuvent se permettre de perdre du temps à bricoler avec quelque chose qui fonctionne. En tant que professionnels, nous devons livrer un produit. Cela signifie que nous obtenons quelque chose qui fonctionne, vérifions que cela fonctionne et passons au travail suivant.

Graham
la source
6

Cela promeut l'idée que le code devrait être parfait et qu'aucun bogue n'existerait - ce qui dans le monde réel est sûrement impossible pour un programme de toute taille.

Pas vrai. pourquoi penses-tu que c'est impossible? Voici un exemple de programme qui fonctionne:

public class MyProgram {
  public boolean alwaysTrue() {
    return true;
  }

  @Test
  public void testAlwaysTrue() {
    assert(alwaysTrue() == true);
  }
}

Il est décourageant d’imaginer des tests unitaires qui échoueront. Ou bien, proposez des tests unitaires difficiles à résoudre.

Dans ce cas, il peut ne pas s'agir d'un test unitaire, mais d'un test d'intégration s'il est compliqué

Si, à un moment donné, tous les tests unitaires réussissent, il n’ya pas de tableau général de l’état du logiciel à un moment donné. Il n'y a pas de feuille de route / objectif.

vrai, cela s’appelle un test unitaire pour une raison, il vérifie une petite unité de code.

Cela dissuade d'écrire les tests unitaires en amont, avant la mise en œuvre.

Les développeurs volontédissuader d'écrire des tests s'ils ne comprennent pas ses avantagespar nature (sauf s'ils venaient de l'assurance qualité)

utilisateur7294900
la source
«Les développeurs dissuaderont [sic] d’écrire des tests de par leur nature», ce qui est un non-sens. Je travaille dans une société entière de développeurs qui pratiquent le TDD et le BDD.
RubberDuck
@RubberDuck J'ai essayé de répondre à un "fait" en question et j'exagérais. Je vais mettre à jour
utilisateur7294900
"X sera dissuadé de faire Y s'ils ne comprennent pas les avantages de Y" s'applique à n'importe quel X ou Y, donc cette déclaration n'est probablement pas particulièrement utile. Il serait probablement plus logique d'expliquer les avantages de la rédaction des tests, et plus précisément de le faire au départ.
Dukeling
2
"impossible pour un programme de toute taille" ne signifie pas "tous les programmes, quelle que soit leur taille", cela signifie "tout programme important (dont la durée n'est pas triviale)" Votre tentative de contre-exemple est inapplicable, car elle n'est pas t un programme important et utile.
Ben Voigt
@BenVoigt Je ne pense pas que je devrais donner un "programme important" comme réponse.
user7294900
4

Cela promeut l'idée que le code devrait être parfait et qu'aucun bogue ne devrait exister

Très certainement pas. Cela favorise l'idée que vos tests ne doivent pas échouer, rien de plus et rien de moins. Supposer que le fait de passer des tests (même beaucoup d’entre eux) ne dit rien au sujet du «parfait» ou du «pas de bugs» est une erreur. Décider de la profondeur ou de la profondeur de vos tests est une partie importante de la rédaction de bons tests et la raison pour laquelle nous avons des catégories de tests distinctes (tests "unitaires", tests d'intégration, "scénarios" au sens du concombre, etc.).

Il est décourageant d’imaginer des tests unitaires qui échoueront. Ou bien, proposez des tests unitaires difficiles à résoudre.

Dans le développement piloté par les tests, il est obligatoire que tous les tests unitaires échouent avant de commencer à coder. C'est ce qu'on appelle "cycle rouge-vert" (ou "cycle rouge-vert-refactor").

  • Sans échec du test, vous ne savez pas si le code est réellement testé par le test. Les deux pourraient ne pas être liés du tout.
  • En changeant le code à exactement faire le tour d'essai du rouge au vert, rien de plus et rien de moins, vous pouvez être sûr que votre code fait ce qu'il est censé faire, et pas beaucoup plus (que vous pourriez jamais besoin).

Si, à un moment donné, tous les tests unitaires réussissent, il n’ya pas de tableau général de l’état du logiciel à un moment donné. Il n'y a pas de feuille de route / objectif.

Les tests sont plutôt une sorte de micro-objectif. Dans le développement piloté par les tests, le programmeur écrira d'abord un test (singulier), puis visera clairement à implémenter du code; puis le prochain test, et ainsi de suite.

La fonction des tests n'est pas d'être complète avant l'écriture du code.

Lorsque cela est fait correctement, dans un langage et avec une bibliothèque de tests adaptée à cette approche, cela peut réellement accélérer énormément le développement, car les messages d'erreur (exceptions / stacktraces) peuvent indiquer directement au développeur où il doit travailler. suivant.

Cela dissuade d'écrire les tests unitaires en amont, avant la mise en œuvre.

Je ne vois pas en quoi cette affirmation serait vraie. La rédaction de tests devrait idéalement faire partie de la mise en œuvre.

Est-ce que j'ai râté quelque chose? Pourquoi les organisations s'attendent-elles à ce que tous les tests unitaires réussissent?

Parce que les organisations s'attendent à ce que les tests soient pertinents pour le code. Écrire des tests qui réussissent signifie que vous avez documenté une partie de votre application et prouvé que celle-ci fait ce qu’il dit (le test). Rien de plus et rien de moins.

En outre, une très grande partie des tests est la "régression". Vous voulez pouvoir développer ou refactoriser un nouveau code en toute confiance. Avoir une grande quantité de tests verts vous permet de le faire.

Cela va du niveau organisationnel au niveau psychologique. Un développeur qui sait que ses tests risquent fort de tromper ses erreurs sera beaucoup plus libre de proposer des solutions intelligentes et audacieuses aux problèmes qu’il doit résoudre. D'un autre côté, un développeur qui n'a pas de test va, après un certain temps, être paralysé (par crainte) parce qu'il ne sait jamais si un changement qu'il fait casse le reste de l'application.

N'est-ce pas vivre dans un monde de rêve?

Travailler avec une application pilotée par les tests est une pure joie - à moins que vous n'aimiez tout simplement pas le concept pour une raison quelconque ("plus d'effort", etc.) que nous pourrons aborder dans une autre question.

Et cela ne décourage-t-il pas réellement une compréhension réelle du code?

Absolument pas, pourquoi le ferait-il?

Vous trouvez de nombreux projets open source de grande taille (pour lesquels la gestion de la "compréhension" et du savoir-faire en matière de code constituent un sujet très pressant) qui utilisent réellement les tests comme documentation principale du logiciel, en plus des tests: fournissez également des exemples réels, fonctionnels et syntaxiquement corrects pour les utilisateurs ou les développeurs de l’application / bibliothèque. Cela fonctionne souvent à merveille.

Évidemment, écrire de mauvais tests est mauvais. Mais cela n'a rien à voir avec la fonction de test en soi.

AnoE
la source
3

(De mes commentaires originaux)

Il existe une différence entre les fonctionnalités requises et les objectifs futurs. Les tests concernent les fonctionnalités requises: ils sont précis, formels, exécutables et s'ils échouent, le logiciel ne fonctionne pas. Les objectifs futurs peuvent ne pas être précis ni formels, et encore moins exécutables, il est donc préférable de les laisser en langage naturel, comme dans les outils de suivi des problèmes / bogues, la documentation, les commentaires, etc.

En guise d’exercice, essayez de remplacer l’expression "test unitaire" dans votre question par "erreur du compilateur" (ou "erreur de syntaxe", s’il n’ya pas de compilateur). Il est évident qu’une version ne devrait pas avoir d’erreurs de compilation, car elle serait inutilisable; Pourtant, les erreurs de compilation et les erreurs de syntaxe sont la situation normale sur la machine d'un développeur lorsqu'il écrit du code. Les erreurs disparaissent seulement quand elles sont finies. et c'est exactement quand le code doit être poussé. Remplacez maintenant "erreur du compilateur" dans ce paragraphe par "test unitaire" :)

Warbo
la source
2

Le but des tests automatisés est de vous dire quand vous avez cassé quelque chose le plus tôt possible . Le workflow ressemble un peu à ceci:

  1. Faire un changement
  2. Construisez et testez votre changement (idéalement automatiquement)
  3. Si les tests échouent, cela signifie que vous avez cassé quelque chose qui fonctionnait auparavant.
  4. si les tests réussissent, vous devez être sûr que votre modification n'introduit aucune nouvelle régression (en fonction de la couverture du test).

Si vos tests échouaient déjà, alors l'étape 3 ne fonctionnerait pas aussi efficacement - les tests échoueront, mais vous ne savez pas si cela signifie que vous avez cassé quelque chose ou pas sans enquêter. Vous pourriez peut-être compter le nombre de tests ayant échoué, mais une modification pourrait corriger un bogue et en casser un autre, ou bien un test pourrait commencer à échouer pour une raison différente. Cela signifie que vous devez attendre un certain temps avant de savoir si quelque chose a été cassé, jusqu'à ce que tous les problèmes aient été résolus ou jusqu'à ce que chaque test ayant échoué ait été examiné.

La possibilité, pour les tests unitaires, de détecter le plus tôt possible les nouveaux bogues introduits est l’essentiel des tests automatisés: plus un défaut disparaît longtemps, plus il est onéreux de le réparer.

Cela promeut l'idée que le code doit être parfait et qu'il ne doit pas exister de bogues.
Il est décourageant d'imaginer des tests unitaires qui échoueront.

Des tests pour des choses qui ne fonctionnent pas ne vous dites pas quoi que ce soit - écrire des tests unitaires pour des choses qui font le travail, ou que vous êtes sur le point de corriger. Cela ne signifie pas que votre logiciel est sans défaut, cela signifie qu'aucun des défauts pour lesquels vous aviez précédemment écrit des tests unitaires ne s'est reproduit.

Il dissuade d'écrire les tests unitaires en amont

Si cela fonctionne pour vous, écrivez les tests dès le départ, mais ne les enregistrez pas dans votre maître / coffre jusqu'à ce qu'ils réussissent.

Si, à un moment donné, tous les tests unitaires réussissent, il n’ya pas de tableau général de l’état du logiciel à un moment donné. Il n'y a pas de feuille de route / objectif.

Les tests unitaires ne servent pas à définir une feuille de route / un objectif, utilisez plutôt un backlog pour cela? Si tous vos tests réussissent, la "grande image" est que votre logiciel n'est pas en panne (si la couverture de vos tests est bonne). Bien joué!

Justin
la source
2

Les réponses existantes sont certes bonnes, mais je n'ai vu personne aborder cette idée fausse fondamentale dans la question:

à tout moment, tous les tests unitaires doivent réussir

Non, ce ne sera certainement pas vrai. Pendant que je développe un logiciel, NCrunch est le plus souvent brun (échec de la construction) ou rouge (test ayant échoué).

NCrunch doit être vert (tous les tests réussis ), c'est quand je suis prêt à envoyer un commit au serveur de contrôle de code source, car à ce moment-là, d'autres personnes peuvent créer une dépendance à mon code.

Cela alimente également le sujet de la création de nouveaux tests: les tests doivent affirmer la logique et le comportement du code. Conditions aux limites, conditions de défaut, etc. Lorsque j'écris de nouveaux tests, j'essaie d'identifier ces "points chauds" dans le code.

Les tests unitaires expliquent comment mon code doit être appelé - conditions préalables, résultats attendus, etc.

Si un test est interrompu suite à une modification, je dois décider si le code ou le test est erroné.


En passant, les tests unitaires vont parfois de pair avec le développement piloté par les tests. L'un des principes de la TDD est que les tests cassés sont vos points de repère. Lorsqu'un test échoue, vous devez corriger le code pour que le test réussisse. Voici un exemple concret du début de cette semaine:

Contexte : J'ai écrit et supporte maintenant une bibliothèque utilisée par nos développeurs pour valider les requêtes Oracle. Nous avions des tests qui affirmaient que la requête correspondait à une valeur attendue, ce qui rendait la casse importante (ce n'était pas Oracle) et approuvait joyeusement les requêtes non valides tant qu'elles correspondaient complètement à la valeur attendue.

Au lieu de cela, ma bibliothèque analyse la requête à l'aide de la syntaxe Antlr et Oracle 12c, puis encapsule diverses assertions sur l'arbre de syntaxe lui-même. Des choses comme, c'est valide (aucune erreur d'analyse n'a été générée), tous ses paramètres sont satisfaits par la collection de paramètres, toutes les colonnes attendues lues par le lecteur de données sont présentes dans la requête, etc. Tous ces éléments ont été glissés jusqu'à production à différents moments.

Un de mes collègues ingénieurs m'a envoyé lundi une question qui avait échoué (ou plutôt, qui avait réussi alors qu'il aurait dû échouer) au cours du week-end. Ma bibliothèque a dit que la syntaxe était correcte, mais elle a explosé lorsque le serveur a essayé de l'exécuter. Et quand il a regardé la requête, il était évident pourquoi:

UPDATE my_table(
SET column_1 = 'MyValue'
WHERE id_column = 123;

J'ai chargé le projet et ajouté un test unitaire affirmant que cette requête ne devait pas être valide. De toute évidence, le test a échoué.

Ensuite, je débogués le test défaillant, par le code entrai où je m'y attendais à jeter l'exception, et compris que Antlr a soulevé une erreur sur les paren ouvertes, mais pas en quelque sorte le code précédent attendait. J'ai modifié le code, vérifié que le test était maintenant vert (réussi) et qu'aucun autre utilisateur n'avait été interrompu, engagé et poussé.

Cela a pris peut-être 20 minutes et, au cours du processus, j’ai considérablement amélioré la bibliothèque car elle supportait désormais toute une gamme d’erreurs qu’elle ignorait auparavant. Si je n'avais pas de tests unitaires pour la bibliothèque, la recherche et la résolution du problème auraient pu prendre des heures.

Galactic Cowboy
la source
0

Un point qui, à mon avis, ne ressort pas des réponses précédentes est qu’il existe une différence entre les tests internes et les tests externes (et je pense que de nombreux projets ne sont pas assez attentifs pour distinguer les deux). Un test interne vérifie que certains composants internes fonctionnent correctement. un test externe montre que le système dans son ensemble fonctionne comme il se doit. Il est tout à fait possible, bien sûr, que les composants défaillants n'entraînent aucune défaillance du système (il existe peut-être une fonction du composant que le système n'utilise pas ou le système récupère éventuellement d'une défaillance du système d'exploitation. composant). Une défaillance de composant qui ne provoque pas de défaillance du système ne doit pas vous empêcher de vous libérer.

J'ai vu des projets paralysés par trop de tests de composants internes. Chaque fois que vous essayez d'implémenter une amélioration des performances, vous effectuez des dizaines de tests, car vous modifiez le comportement des composants sans modifier réellement le comportement visible de l'extérieur du système. Cela conduit à un manque d'agilité dans l'ensemble du projet. Je pense que l'investissement dans les tests de systèmes externes rapporte généralement beaucoup mieux que l'investissement dans les tests de composants internes, en particulier lorsqu'il s'agit de composants de très bas niveau.

Lorsque vous suggérez que les tests unitaires échoués n'ont pas vraiment d'importance, je me demande si c'est ce que vous avez en tête? Vous devriez peut-être évaluer la valeur des tests unitaires et supprimer ceux qui causent plus de problèmes qu'ils ne valent, tout en vous concentrant davantage sur des tests qui vérifient le comportement visible de l'application de l'extérieur.

Michael Kay
la source
Je pense que ce que vous décrivez comme des "tests externes" sont souvent décrits ailleurs comme des tests "d'intégration".
GalacticCowboy
Oui, mais j'ai rencontré des différences de terminologie. Pour certaines personnes, les tests d'intégration concernent davantage la configuration logicielle / matérielle / réseau déployée, alors que je parle du comportement externe d'un logiciel que vous développez.
Michael Kay
0

"mais à tout moment, tous les tests unitaires doivent réussir"

Si c'est l'attitude de votre entreprise, c'est un problème. À un moment donné, à savoir lorsque nous déclarons que le code est prêt à passer à l'environnement suivant, tous les tests unitaires doivent réussir. Mais au cours du développement, nous devrions systématiquement nous attendre à l'échec de nombreux tests unitaires.

Aucune personne raisonnable ne s'attend à ce qu'un programmeur travaille parfaitement du premier coup. Nous nous attendons raisonnablement à ce qu'il continue à travailler jusqu'à ce qu'il n'y ait plus de problèmes connus.

"Il est décourageant d'imaginer des tests unitaires qui échoueront. Ou bien de proposer des tests unitaires difficiles à corriger." Si une personne de votre organisation pense qu’elle ne devrait pas mentionner un test possible car elle pourrait échouer et lui demander plus de travail pour le réparer, cette personne n’est absolument pas qualifiée pour son travail. C'est une attitude désastreuse. Voudriez-vous un médecin qui dit: "Quand je fais une chirurgie, je ne vérifie pas délibérément si les points de suture sont corrects, parce que si je vois qu'ils ne le sont pas, je vais devoir les refaire et que va ralentir la fin de l'opération "?

Si l'équipe est hostile aux programmeurs qui identifient des erreurs avant que le code ne passe en production, vous avez un réel problème avec l'attitude de cette équipe. Si la direction punit les programmeurs qui identifient des erreurs qui ralentissent la livraison, il y a de fortes chances que votre entreprise se dirige vers la faillite.

Oui, il est certainement vrai que parfois des personnes rationnelles disent: "Nous approchons de la date limite, il s'agit d'un problème trivial et il ne vaut pas la peine de consacrer les ressources pour le moment à le résoudre." Mais vous ne pouvez pas prendre cette décision rationnellement si vous ne le savez pas. Examiner froidement une liste d’erreurs et attribuer des priorités et des calendriers pour les corriger est rationnel. Se faire délibérément ignorer des problèmes pour ne pas avoir à prendre cette décision est stupide. Pensez-vous que le client ne le saura pas simplement parce que vous ne voulez pas savoir?

Geai
la source
-7

Il s'agit d'un exemple spécifique de biais de confirmation , dans lequel les gens ont tendance à rechercher des informations qui confirment leurs croyances existantes.

Un exemple célèbre de ce phénomène se trouve dans le jeu 2,4,6.

  • J'ai la règle en tête qu'une série de trois nombres réussira ou échouera,
  • 2,4,6 est un laissez-passer
  • vous pouvez lister des ensembles de trois nombres, et je vous dirai s’ils réussissent ou échouent.

La plupart des gens choisissent une règle, par exemple "l’écart entre le premier et le deuxième nombre est le même que celui entre le deuxième et le troisième".

Ils vont tester quelques chiffres:

  • 4, 8, 12? Passer
  • 20, 40, 60? Passer
  • 2 1004 2006? Passer

Ils disent "Oui, chaque observation confirme mon hypothèse, ça doit être vrai." Et annoncez leur règle à la personne qui donne l'énigme.

Mais ils n'ont jamais reçu un seul «échec» à un ensemble de trois numéros. La règle aurait pu être la suivante: «les trois chiffres doivent être des chiffres» pour toutes les informations dont ils disposent.

La règle est en réalité juste que les nombres sont dans l'ordre croissant. Les gens ne résolvent généralement cette énigme que s'ils testent leur échec. La plupart des gens se trompent en choisissant une règle plus spécifique et en ne testant que les nombres qui répondent à cette règle.

Quant aux raisons pour lesquelles les gens tombent sous le biais du biais de confirmation et peuvent considérer que les tests unitaires échouent comme preuve d’un problème, de nombreux psychologues peuvent expliquer le biais de confirmation mieux que moi. se prouver le contraire.

Scott
la source
2
Comment est-ce pertinent à la question? Les tests unitaires échoués sont la preuve d'un problème, par définition.
Frax
1
Vous pouvez absolument faire en sorte que les tests unitaires nécessitant que le système testé passe en mode échec. Ce n'est pas la même chose que de ne jamais voir un test échouer. C'est aussi pourquoi le TDD est spécifié comme cycle "Rouge-> Vert-> Refactor"
Caleth