web-dev-qa-db-fra.com

Les tests unitaires peuvent-ils être ajoutés avec succès dans un projet de production existant? Si oui, comment et cela en vaut-il la peine?

J'envisage sérieusement d'ajouter des tests unitaires à un projet existant en cours de production. Il a été lancé il y a 18 mois avant que je puisse vraiment voir les avantages du TDD (face Palm), alors maintenant c'est une solution assez large avec un certain nombre de projets et je n'ai pas la moindre idée par où commencer pour ajouter des tests unitaires. Ce qui me fait penser à cela, c'est qu'occasionnellement un vieux bogue semble refaire surface, ou qu'un bogue est enregistré comme corrigé sans que vraiment soit corrigé. Les tests unitaires réduiraient ou empêcheraient ces problèmes de se produire.

En lisant similaire questions sur SO, j'ai vu des recommandations telles que commencer par le suivi des bogues et écrire un cas de test pour chaque bogue pour empêcher la régression. Cependant, je crains que je finisse par manquer la vue d'ensemble et que je finisse par manquer des tests fondamentaux qui auraient été inclus si j'avais utilisé TDD dès le départ.

Existe-t-il des processus/étapes qui doivent être respectés afin de s'assurer qu'une solution existante est correctement testée à l'unité et pas seulement encombrée? Comment puis-je m'assurer que les tests sont de bonne qualité et ne sont pas seulement un cas n'importe quel test vaut mieux que pas de tests.

Je suppose donc que ce que je demande aussi, c'est;

  • Vaut-il la peine pour une solution existante en production?
  • Serait-il préférable d'ignorer les tests de ce projet et de l'ajouter dans une éventuelle réécriture future?
  • Quoi de plus bénéfique; passer quelques semaines à ajouter des tests ou quelques semaines à ajouter des fonctionnalités?

(Évidemment, la réponse au troisième point dépend entièrement de si vous parlez à la direction ou à un développeur)


Raison de la prime

Ajouter une prime pour essayer d'attirer un plus large éventail de réponses qui confirment non seulement mes soupçons actuels selon lesquels c'est une bonne chose à faire, mais aussi de bonnes raisons contre.

J'ai l'intention d'écrire cette question plus tard avec des avantages et des inconvénients pour essayer de montrer à la direction qu'il vaut la peine de consacrer des heures à passer le développement futur du produit à TDD. Je veux aborder ce défi et développer mon raisonnement sans mon propre point de vue biaisé.

131
djdd87

J'ai introduit des tests unitaires pour coder des bases qui n'en avaient pas auparavant. Le dernier grand projet auquel j'ai participé, où je l'ai fait, le produit était déjà en production avec zéro test unitaire lorsque je suis arrivé dans l'équipe. Quand je suis parti - 2 ans plus tard - nous avions plus de 4500 tests, ce qui donnait environ 33% de couverture de code dans une base de code avec 230 000 + production LOC (application Win-Forms financière en temps réel). Cela peut sembler faible, mais le résultat a été une amélioration significative de la qualité du code et du taux de défauts - ainsi qu'une amélioration du moral et de la rentabilité.

Cela peut être fait lorsque vous avez à la fois une compréhension et un engagement précis des parties impliquées.

Tout d'abord, il est important de comprendre que les tests unitaires sont une compétence en soi. Vous pouvez être un programmeur très productif selon les normes "conventionnelles" et avoir du mal à écrire des tests unitaires d'une manière qui évolue dans un projet plus vaste.

De plus, et spécifiquement pour votre situation, l'ajout de tests unitaires à une base de code existante qui n'a aucun test est également une compétence spécialisée en soi. À moins que vous ou quelqu'un de votre équipe ayez une expérience réussie dans l'introduction de tests unitaires à une base de code existante, je dirais que la lecture Livre de Feather est une exigence (non facultative ou fortement recommandée).

Faire la transition vers les tests unitaires de votre code est un investissement dans les personnes et les compétences tout autant que dans la qualité de la base de code. Comprendre cela est très important en termes de mentalité et de gestion des attentes.

Maintenant, pour vos commentaires et questions:

Cependant, je crains que je finisse par manquer la vue d'ensemble et que je finisse par manquer des tests fondamentaux qui auraient été inclus si j'avais utilisé TDD dès le départ.

Réponse courte: Oui, vous manquerez les tests et oui, ils pourraient ne pas ressembler initialement à ce qu'ils auraient dans une situation de champ vert.

La réponse de niveau plus profond est la suivante: cela n'a pas d'importance. Vous commencez sans tests. Commencez à ajouter des tests et à refactoriser au fur et à mesure. Au fur et à mesure que les niveaux de compétence s'améliorent, commencez à relever la barre pour tout le nouveau code écrit ajouté à votre projet. Continuez à vous améliorer etc ...

Maintenant, en lisant entre les lignes ici, j'ai l'impression que cela vient de l'état d'esprit de "la perfection comme excuse pour ne pas agir". Une meilleure mentalité consiste à se concentrer sur la confiance en soi. Donc, comme vous ne savez peut-être pas encore comment le faire, vous découvrirez comment faire en remplissant les blancs. Il n'y a donc aucune raison de s'inquiéter.

Encore une fois, c'est une compétence. Vous ne pouvez pas passer de zéro test à TDD-perfection en une seule approche de "processus" ou "étape par étape" de livre de cuisine de manière linéaire. Ce sera un processus. Vos attentes doivent être de progresser et de s'améliorer progressivement et progressivement. Il n'y a pas de pilule magique.

La bonne nouvelle est que, au fil des mois (et même des années), votre code commencera progressivement à devenir un "bon" code bien factorisé et bien testé.

En remarque. Vous constaterez que le principal obstacle à l'introduction de tests unitaires dans une ancienne base de code est le manque de cohésion et les dépendances excessives. Vous constaterez donc probablement que la compétence la plus importante sera de savoir comment briser les dépendances existantes et le code de découplage, plutôt que d'écrire les tests unitaires eux-mêmes.

Existe-t-il des processus/étapes à respecter afin de garantir qu'une solution existante est correctement testée à l'unité et non pas simplement intégrée?

Sauf si vous l'avez déjà, configurez un serveur de build et configurez une build d'intégration continue qui s'exécute à chaque archivage, y compris tous les tests unitaires avec une couverture de code.

Formez votre peuple.

Commencez quelque part et commencez à ajouter des tests pendant que vous progressez du point de vue du client (voir ci-dessous).

Utilisez la couverture du code comme référence pour déterminer la proportion de votre base de code de production qui est testée.

Le temps de construction doit toujours être RAPIDE. Si votre temps de construction est lent, vos compétences de test d'unité sont à la traîne. Trouvez les tests lents et améliorez-les (découpler le code de production et tester de manière isolée). Bien écrit, vous devriez facilement pouvoir avoir plusieurs milliers de tests unitaires et terminer une construction en moins de 10 minutes (~ 1-quelques ms/test est une bonne mais très approximative directive, quelques exceptions peuvent s'appliquer comme du code utilisant la réflexion, etc. ).

Inspectez et adaptez.

Comment puis-je m'assurer que les tests sont de bonne qualité et ne sont pas simplement un cas de n'importe quel test vaut mieux que pas de tests.

Votre propre jugement doit être votre principale source de réalité. Aucune métrique ne peut remplacer la compétence.

Si vous n'avez pas cette expérience ou ce jugement, envisagez d'engager quelqu'un qui en a.

Deux indicateurs secondaires approximatifs sont la couverture totale du code et la vitesse de construction.

Vaut-il la peine pour une solution existante en production?

Oui. La grande majorité de l'argent dépensé pour un système ou une solution sur mesure est dépensée après sa mise en production. Et investir dans la qualité, les gens et les compétences ne doit jamais être démodé.

Serait-il préférable d'ignorer les tests de ce projet et de l'ajouter dans une éventuelle réécriture future?

Vous devez prendre en considération non seulement l'investissement en personnel et en compétences, mais surtout le coût total de possession et la durée de vie prévue du système.

Ma réponse personnelle serait "oui bien sûr" dans la majorité des cas parce que je sais que c'est tellement mieux, mais je reconnais qu'il pourrait y avoir des exceptions.

Quoi de plus bénéfique; passer quelques semaines à ajouter des tests ou quelques semaines à ajouter des fonctionnalités?

Ni. Votre approche devrait être d'ajouter des tests à votre base de code PENDANT que vous progressez en termes de fonctionnalité.

Encore une fois, il s'agit d'un investissement dans les personnes, les compétences ET la qualité de la base de code et en tant que tel, il faudra du temps. Les membres de l'équipe doivent apprendre à briser les dépendances, à écrire des tests unitaires, à apprendre de nouveaux habbits, à améliorer la discipline et la sensibilisation à la qualité, à améliorer la conception de logiciels, etc. Il est important de comprendre que lorsque vous commencez à ajouter des tests, les membres de votre équipe ne le font probablement pas avoir ces compétences au niveau dont elles ont besoin pour que cette approche soit réussie, donc arrêter de progresser pour passer tout le temps à ajouter beaucoup de tests ne fonctionnera tout simplement pas.

De plus, l'ajout de tests unitaires à une base de code existante de n'importe quelle taille de projet importante est une GRANDE entreprise qui requiert engagement et persévérance. Vous ne pouvez pas changer quelque chose de fondamental, attendez-vous à beaucoup d'apprentissage en cours de route et demandez à votre sponsor de ne pas attendre de retour sur investissement en interrompant le flux de valeur commerciale. Ça ne volera pas, et franchement ça ne devrait pas.

Troisièmement, vous voulez inculquer de solides valeurs de concentration commerciale à votre équipe. La qualité ne vient jamais aux dépens du client et vous ne pouvez pas aller vite sans qualité. De plus, le client vit dans un monde en évolution et votre travail consiste à lui faciliter l'adaptation. L'alignement client nécessite à la fois la qualité et le flux de valeur commerciale.

Ce que vous faites, c'est rembourser la dette technique. Et vous le faites tout en continuant à répondre à vos clients en constante évolution. Au fur et à mesure que la dette est remboursée, la situation s'améliore et il est plus facile de mieux servir le client et d'offrir plus de valeur. Etc. Cet élan positif est ce que vous devriez viser car il souligne les principes du rythme durable et maintiendra et améliorera le moral - à la fois pour votre équipe de développement, votre client et vos parties prenantes.

J'espère que ça t'as aidé

166
Mahol25
  • Vaut-il la peine pour une solution existante en production?

Oui!

  • Serait-il préférable d'ignorer les tests de ce projet et de l'ajouter dans une éventuelle réécriture future?

Non!

  • Quoi de plus bénéfique; passer quelques semaines à ajouter des tests ou quelques semaines à ajouter des fonctionnalités?

L'ajout de tests (en particulier les tests automatisés) rend beaucoup plus facile à faire fonctionner le projet à l'avenir, et il le rend considérablement il est moins probable que vous envoyiez des problèmes stupides à l'utilisateur.

Les tests à mettre en place a priori sont ceux qui vérifient si ce que vous pensez que l'interface publique de votre code (et chaque module qu'il contient) fonctionne comme vous pense. Si vous le pouvez, essayez également d'induire chaque mode d'échec isolé que vos modules de code devraient avoir (notez que cela peut être non trivial, et vous devez faire attention à ne pas vérifier trop attentivement comment les choses échouent, par exemple, vous ne voulez pas vraiment pour faire des choses comme compter le nombre de messages de journal produits en cas d'échec, car il suffit de vérifier qu'il est enregistré).

Ensuite, mettez un test pour chaque bogue actuel dans votre base de données de bogues qui induit exactement le bogue et qui passera lorsque le bogue sera corrigé. Puis corrigez ces bugs! :-)

L'ajout de tests coûte du temps à l'avance, mais vous êtes remboursé plusieurs fois à l'arrière-plan, car votre code finit par être de bien meilleure qualité. Cela est extrêmement important lorsque vous essayez de livrer une nouvelle version ou d'effectuer une maintenance.

24
Donal Fellows

Le problème avec les tests unitaires de mise à niveau est que vous vous rendrez compte que vous n'avez pas pensé à injecter une dépendance ici ou à utiliser une interface là-bas, et d'ici peu, vous réécrirez le composant entier. Si vous avez le temps de le faire, vous vous construirez un joli filet de sécurité, mais vous auriez pu introduire de subtils bugs en cours de route.

J'ai été impliqué dans de nombreux projets qui nécessitaient vraiment des tests unitaires dès le premier jour, et il n'y a pas de moyen facile de les faire entrer, à part une réécriture complète, qui ne peut généralement pas être justifiée lorsque le code fonctionne et gagne déjà de l'argent. Récemment, j'ai eu recours à l'écriture de scripts PowerShell qui exercent le code de manière à reproduire un défaut dès qu'il est généré, puis à conserver ces scripts comme une suite de tests de régression pour de nouveaux changements sur toute la ligne. De cette façon, vous pouvez au moins commencer à créer des tests pour l'application sans trop la modifier, cependant, ce sont plus des tests de régression de bout en bout que des tests unitaires appropriés.

15
fletcher

Je suis d'accord avec ce que la plupart des autres ont dit. L'ajout de tests au code existant est précieux. Je ne serai jamais en désaccord avec ce point, mais je voudrais ajouter une mise en garde.

Bien que l'ajout de tests au code existant soit précieux, il a un coût. Cela se fait au prix de et non de nouvelles fonctionnalités. L'équilibre de ces deux éléments dépend entièrement du projet et il existe un certain nombre de variables.

  • Combien de temps vous faudra-t-il pour tester tout ce code? Journées? Semaines? Mois? Ans?
  • Pour qui écrivez-vous ce code? Payer les clients? Un enseignant? Un projet open source?
  • À quoi ressemble ton horaire? Avez-vous des délais à respecter? Avez-vous des délais?

Encore une fois, permettez-moi de souligner que les tests sont précieux et que vous devriez travailler pour mettre votre ancien code sous test. C'est vraiment plus une question d'approche. Si vous pouvez vous permettre de tout laisser tomber et de tester tout votre ancien code, faites-le. Si ce n'est pas réaliste, voici ce que vous devez faire à tout le moins

  • Tout nouveau code que vous écrivez doit être entièrement sous test unitaire
  • Tout ancien code que vous touchez (correction de bogue, extension, etc.) doit être soumis à un test unitaire

De plus, ce n'est pas une proposition tout ou rien. Si vous avez une équipe de, disons, quatre personnes et que vous pouvez respecter vos délais en affectant une ou deux personnes à des tests hérités, faites-le.

Modifier:

J'ai l'intention d'écrire cette question plus tard avec des avantages et des inconvénients pour essayer de montrer à la direction qu'il vaut la peine de consacrer des heures à passer le développement futur du produit à TDD.

C'est comme demander "Quels sont les avantages et les inconvénients de l'utilisation du contrôle de code source?" ou "Quels sont les avantages et les inconvénients d'interroger des personnes avant de les embaucher?" ou "Quels sont les avantages et les inconvénients de la respiration?"

Parfois, il n'y a qu'un seul côté à l'argument. Vous devez avoir des tests automatisés d'une certaine forme pour tout projet de toute complexité. Non, les tests ne s'écrivent pas eux-mêmes et, oui, cela prendra un peu plus de temps pour sortir les choses. Mais à long terme, cela prendra plus de temps et coûtera plus cher pour corriger les bugs après coup que d'écrire des tests à l'avance. Période. C'est tout ce qu'on peut en dire.

13
haydenmuhl

Lorsque nous avons commencé à ajouter des tests, c'était à une base de code de dix ans, environ un million de lignes, avec beaucoup trop de logique dans l'interface utilisateur et dans le code de rapport.

L'une des premières choses que nous avons faites (après avoir installé un serveur de build continu) a été d'ajouter des tests de régression. Il s'agissait de tests de bout en bout.

  • Chaque suite de tests commence par initialiser la base de données à un état connu. Nous avons en fait des dizaines d'ensembles de données de régression que nous conservons dans Subversion (dans un référentiel distinct de notre code, en raison de la taille). FixtureSetUp de chaque test copie l'un de ces jeux de données de régression dans une base de données temporaire, puis s'exécute à partir de là.
  • La configuration du dispositif de test exécute ensuite un processus dont les résultats nous intéressent. (Cette étape est facultative - certains tests de régression existent uniquement pour tester les rapports.)
  • Ensuite, chaque test exécute un rapport, génère le rapport dans un fichier .csv et compare le contenu de ce fichier .csv à un instantané enregistré. Ces snapshots .csvs sont stockés dans Subversion à côté de chaque jeu de données de régression. Si la sortie du rapport ne correspond pas à l'instantané enregistré, le test échoue.

Le but des tests de régression est de vous dire si quelque chose change. Cela signifie qu'ils échouent si vous avez cassé quelque chose, mais ils échouent également si vous avez modifié quelque chose exprès (dans ce cas, le correctif consiste à mettre à jour le fichier d'instantané). Vous ne savez pas que les fichiers d'instantanés sont même corrects - il peut y avoir des bogues dans le système (puis lorsque vous corrigez ces bogues, les tests de régression échouent).

Néanmoins, les tests de régression ont été une énorme victoire pour nous. Presque tout dans notre système a un rapport, donc en passant quelques semaines à obtenir un faisceau de test autour des rapports, nous avons pu obtenir un certain niveau de couverture sur une grande partie de notre base de code. La rédaction des tests unitaires équivalents aurait pris des mois ou des années. (Les tests unitaires nous auraient donné une bien meilleure couverture et auraient été beaucoup moins fragiles; mais je préférerais avoir quelque chose maintenant, plutôt que d'attendre des années pour la perfection.)

Ensuite, nous sommes revenus et avons commencé à ajouter des tests unitaires lorsque nous avons corrigé des bogues, ajouté des améliorations ou besoin de comprendre du code. Les tests de régression ne suppriment en rien la nécessité de tests unitaires; ils sont juste un filet de sécurité de premier niveau, de sorte que vous obtenez rapidement un certain niveau de couverture de test. Ensuite, vous pouvez commencer le refactoring pour briser les dépendances, vous pouvez donc ajouter des tests unitaires; et les tests de régression vous donnent un niveau de confiance que votre refactoring ne casse rien.

Les tests de régression ont des problèmes: ils sont lents et il y a trop de raisons pour lesquelles ils peuvent se casser. Mais au moins pour nous, ils valaient donc ça valait le coup. Ils ont attrapé d'innombrables bugs au cours des cinq dernières années, et ils les ont attrapés en quelques heures, plutôt que d'attendre un cycle d'assurance qualité. Nous avons toujours ces tests de régression originaux, répartis sur sept machines de construction continue différentes (distinctes de celle qui exécute les tests unitaires rapides), et nous les ajoutons même de temps en temps, car nous avons encore tellement de code que nos 6000 + les tests unitaires ne couvrent pas.

9
Joe White

Ça en vaut vraiment la peine. Notre application possède des règles de validation croisée complexes et nous avons récemment dû apporter des modifications importantes aux règles métier. Nous nous sommes retrouvés avec des conflits qui ont empêché l'utilisateur de sauvegarder. J'ai réalisé qu'il faudrait une éternité pour régler le problème dans l'application (cela prend plusieurs minutes juste pour arriver au point où étaient les problèmes). Je voulais introduire des tests unitaires automatisés et faire installer le framework, mais je n'avais rien fait au-delà de quelques tests factices pour m'assurer que les choses fonctionnaient. Avec les nouvelles règles métier en main, j'ai commencé à écrire des tests. Les tests ont rapidement identifié les conditions à l'origine des conflits et nous avons pu clarifier les règles.

Si vous écrivez des tests qui couvrent les fonctionnalités que vous ajoutez ou modifiez, vous obtiendrez un avantage immédiat. Si vous attendez une réécriture, vous n'aurez peut-être jamais de tests automatisés.

Vous ne devriez pas passer beaucoup de temps à écrire des tests pour des choses existantes qui fonctionnent déjà. La plupart du temps, vous n'avez pas de spécification pour le code existant, donc la principale chose que vous testez est votre capacité de rétro-ingénierie. D'un autre côté, si vous souhaitez modifier quelque chose, vous devez couvrir cette fonctionnalité avec des tests afin que vous sachiez que vous avez correctement effectué les modifications. Et bien sûr, pour les nouvelles fonctionnalités, écrivez les tests qui échouent, puis implémentez les fonctionnalités manquantes.

8
Hugh Brackett

Je vais ajouter ma voix et dire oui, c'est toujours utile!

Il y a cependant quelques distinctions à garder à l'esprit: boîte noire vs boîte blanche, et unité vs fonctionnelle. Puisque les définitions varient, voici ce que j'entends par celles-ci:

  • Black-box = tests qui sont écrits sans connaissance particulière de l'implémentation, fouillant généralement les cas Edge pour s'assurer que les choses se passent comme un utilisateur naïf s'y attendrait.
  • White-box = tests écrits avec connaissance de l'implémentation, qui essaient souvent d'exercer des points de défaillance bien connus.
  • Tests unitaires = tests des unités individuelles (fonctions, modules séparables, etc.). Par exemple: vous assurer que votre classe de tableaux fonctionne comme prévu et que votre fonction de comparaison de chaînes renvoie les résultats attendus pour une large gamme d'entrées.
  • Tests fonctionnels = tests de l'ensemble du système en une seule fois. Ces tests exerceront une grande partie du système à la fois. Par exemple: init, ouvrir une connexion, faire des trucs du monde réel, fermer, terminer. J'aime faire une distinction entre ces tests et les tests unitaires, car ils servent un objectif différent.

Lorsque j'ai ajouté des tests à un produit d'expédition à la fin du jeu, j'ai constaté que j'en tirais le meilleur parti pour les tests boîte blanche et fonctionnels. S'il y a une partie du code que vous savez particulièrement fragile, écrivez des tests en boîte blanche pour couvrir les cas problématiques afin de vous assurer qu'il ne se casse pas deux fois de la même manière. De même, les tests fonctionnels de l'ensemble du système sont une vérification d'utilité utile qui vous aide à vous assurer de ne jamais casser les 10 cas d'utilisation les plus courants.

Les tests en boîte noire et unitaires de petites unités sont également utiles, mais si votre temps est limité, il est préférable de les ajouter tôt. Au moment où vous expédiez, vous avez généralement trouvé (à la dure) la majorité des cas et problèmes Edge que ces tests auraient trouvés.

Comme les autres, je vais également vous rappeler les deux choses les plus importantes à propos de TDD:

  1. La création de tests est un travail continu. Cela ne s'arrête jamais. Vous devez essayer d'ajouter de nouveaux tests chaque fois que vous écrivez du nouveau code ou de modifier le code existant.
  2. Votre suite de tests n'est jamais infaillible! Ne laissez pas le fait d'avoir des tests vous endormir dans un faux sentiment de sécurité. Ce n'est pas parce qu'il passe la suite de tests que cela fonctionne correctement ou que vous n'avez pas introduit de régression subtile des performances, etc.
6
Drew Thaler

Vous ne mentionnez pas le langage d'implémentation, mais si dans Java alors vous pouvez essayer cette approche:

  1. Dans une arborescence source distincte, régression de construction ou tests de `` fumée '', en utilisant un outil pour les générer, ce qui pourrait vous permettre d'atteindre une couverture de près de 80%. Ces tests exécutent tous les chemins de la logique du code et vérifient à partir de là que le code fait toujours exactement ce qu'il fait actuellement (même si un bogue est présent). Cela vous donne un filet de sécurité contre tout changement de comportement par inadvertance lors de la refactorisation nécessaire pour rendre le code facilement testable à la main.

  2. Pour chaque bogue que vous corrigez ou fonctionnalité que vous ajoutez à partir de maintenant, utilisez une approche TDD pour vous assurer que le nouveau code est conçu pour être testable et placez ces tests dans une arborescence de source de test normale.

  3. Le code existant devra également probablement être modifié ou refactorisé pour le rendre testable dans le cadre de l'ajout de nouvelles fonctionnalités; vos tests de fumée vous donneront un filet de sécurité contre les régressions ou les changements subtils involontaires de comportement.

  4. Lorsque vous apportez des modifications (corrections de bogues ou fonctionnalités) via TDD, une fois terminé, il est probable que le test de fumée associé échoue. Vérifiez que les échecs sont comme prévu en raison des modifications apportées et supprimez le test de fumée moins lisible, car votre test unitaire manuscrit a une couverture complète de ce composant amélioré. Assurez-vous que votre couverture de test ne diminue pas mais reste inchangée ou augmente.

  5. Lors de la correction des bogues, écrivez un test unitaire défaillant qui expose le bogue en premier.

4
Chris B

La pertinence d'ajouter des tests unitaires à une application en cours de production dépend du coût de maintenance de l'application. Si l'application a peu de bogues et de demandes d'amélioration, cela ne vaut peut-être pas la peine. OTOH, si l'application est boguée ou fréquemment modifiée, les tests unitaires seront extrêmement bénéfiques.

À ce stade, n'oubliez pas que je parle d'ajouter des tests unitaires de manière sélective, sans essayer de générer une suite de tests similaires à ceux qui existeraient si vous aviez pratiqué le TDD depuis le début. Par conséquent, en réponse à la deuxième moitié de votre deuxième question: assurez-vous d'utiliser TDD sur votre prochain projet, qu'il s'agisse d'un nouveau projet ou d'une réécriture (excuses, mais voici un lien vers un autre livre que vous devriez vraiment lire: Growing Object Oriented Software Guided by Tests )

Ma réponse à votre troisième question est la même que la première: cela dépend du contexte de votre projet.

Intégré à l'intérieur de votre message est une autre question sur la garantie que tous les tests rétro-ajustés sont effectués correctement. La chose importante à assurer est que les tests unitaires sont vraiment unit tests, et cela (le plus souvent) signifie que les tests de retrofit nécessitent de refactoriser le code existant pour permettre le découplage de vos couches/composants (cf. injection de dépendance; inversion de contrôle; stubbing; mocking). Si vous ne le faites pas, vos tests deviennent des tests d'intégration, qui sont utiles, mais moins ciblés et plus fragiles que les vrais tests unitaires.

4
Seb Rose

Je voudrais commencer cette réponse en disant que les tests unitaires sont vraiment importants car ils vous aideront à arrêter les bogues avant qu'ils ne pénètrent en production.

Identifiez les zones de projets/modules où les bogues ont été réintroduits. Commencez par ces projets pour écrire des tests. Il est parfaitement logique d'écrire des tests pour de nouvelles fonctionnalités et pour corriger des bogues.

Vaut-il la peine pour une solution existante en production?

Oui. Vous verrez les effets des bogues et la maintenance devient plus facile

Serait-il préférable d'ignorer les tests de ce projet et de l'ajouter dans une éventuelle réécriture future?

Je recommanderais de commencer si à partir de maintenant.

Quoi de plus bénéfique; passer quelques semaines à ajouter des tests ou quelques semaines à ajouter des fonctionnalités?

Vous posez la mauvaise question. Certainement, la fonctionnalité est plus importante que toute autre chose. Mais, vous devriez plutôt vous demander si passer quelques semaines à ajouter un test rendra mon système plus stable. Est-ce que cela aidera mon utilisateur final? Cela aidera-t-il un nouveau développeur de l'équipe à comprendre le projet et à s'assurer qu'il/elle n'introduit pas de bogue en raison du manque de compréhension de l'impact global d'un changement.

3
P.K

J'aime beaucoup Refactor the Low-suspend Fruit comme réponse à la question de savoir où commencer le refactoring. C'est un moyen de faciliter une meilleure conception sans mordre plus que vous ne pouvez mâcher.

Je pense que la même logique s'applique au TDD - ou simplement aux tests unitaires: écrivez les tests dont vous avez besoin, comme vous en avez besoin; écrire des tests pour le nouveau code; écrire des tests pour les bogues tels qu'ils apparaissent. Vous craignez de négliger les zones les plus difficiles d'accès de la base de code, et c'est certainement un risque, mais comme moyen de commencer: commencez! Vous pouvez atténuer le risque à l'avenir avec des outils de couverture de code, et le risque n'est pas (à mon avis) si grand, de toute façon: si vous couvrez les bogues, couvrez le nouveau code, couvrez le code que vous regardez , alors vous couvrez le code qui a le plus besoin de tests.

3
Carl Manaster

Mise à jour

6 ans après la réponse originale, j'ai une prise légèrement différente.

Je pense qu'il est logique d'ajouter des tests unitaires à tout le nouveau code que vous écrivez - puis de refactoriser les endroits où vous apportez des modifications pour les rendre testables.

Écrire des tests en une seule fois pour tout votre code existant n'aidera pas - mais ne pas écrire de tests pour le nouveau code que vous écrivez (ou les zones que vous modifiez) n'a également aucun sens. Ajouter des tests au fur et à mesure que vous refactorisez/ajoutez des choses est probablement le meilleur moyen d'ajouter des tests et de rendre le code plus maintenable dans un projet existant sans test.

Réponse antérieure

Je vais lever quelques sourcils ici :)

Tout d'abord quel est votre projet - si c'est un compilateur ou un langage ou un framework ou quoi que ce soit qui ne va pas changer de façon fonctionnelle pendant longtemps, alors je pense que c'est absolument fantastique d'ajouter des tests unitaires.

Cependant, si vous travaillez sur une application qui va probablement nécessiter des changements de fonctionnalités (en raison de l'évolution des exigences), il est inutile de prendre cet effort supplémentaire.

Pourquoi?

  1. Les tests unitaires ne couvrent que les tests de code - si le code fait ce pour quoi il est conçu - il ne remplace pas les tests manuels qui doivent de toute façon être effectués (pour découvrir les bogues fonctionnels, les problèmes d'utilisation et tous les autres types de problèmes)

  2. Les tests unitaires coûtent du temps! Maintenant, d'où je viens, c'est un produit précieux - et les entreprises choisissent généralement de meilleures fonctionnalités sur une suite de tests complète.

  3. Si votre application est même utile à distance aux utilisateurs, ils vont demander des modifications - vous aurez donc des versions qui feront les choses mieux, plus rapidement et probablement de nouvelles choses - il peut également y avoir beaucoup de refactoring à mesure que votre code se développe. Le maintien d'une suite complète de tests unitaires dans un environnement dynamique est un casse-tête.

  4. Les tests unitaires n'affecteront pas la qualité perçue de votre produit - la qualité que l'utilisateur voit. Bien sûr, vos méthodes peuvent fonctionner exactement comme elles l'ont fait le jour 1, l'interface entre la couche de présentation et la couche métier peut être vierge - mais devinez quoi? L'utilisateur s'en fiche! Obtenez de vrais testeurs pour tester votre application. Et le plus souvent, ces méthodes et interfaces doivent changer de toute façon, tôt ou tard.

Quoi de plus bénéfique; passer quelques semaines à ajouter des tests ou quelques semaines à ajouter des fonctionnalités? - Il y a beaucoup de choses que vous pouvez faire mieux que d'écrire des tests - Écrire de nouvelles fonctionnalités, améliorer les performances, améliorer la convivialité, écrire de meilleurs manuels d'aide, résoudre les bogues en attente, etc.

Maintenant, ne vous méprenez pas - Si vous êtes absolument certain que les choses ne changeront pas dans les 100 prochaines années, allez-y, assommez-vous et écrivez ces tests. Les tests automatisés sont également une excellente idée pour les API, où vous ne voulez absolument pas casser le code tiers. Partout ailleurs, c'est juste quelque chose qui me fait expédier plus tard!

2
Roopesh Shenoy
  • oui, ça l'est. lorsque vous commencez à ajouter de nouvelles fonctionnalités, cela peut entraîner des modifications de l'ancien code et, en conséquence, c'est une source de bogues potentiels.
  • (voir le premier) avant de commencer à ajouter de nouvelles fonctionnalités, tout (ou presque) le code (idéalement) devrait être couvert par des tests unitaires.
  • (voir le premier et le deuxième) :). une nouvelle fonctionnalité grandiose peut "détruire" l'ancien code travaillé.
2
garik

Oui, cela peut: Essayez simplement de vous assurer que tout le code que vous écrivez à partir de maintenant a un test en place.

Si le code qui est déjà en place doit être modifié et peut être testé, alors faites-le, mais il vaut mieux ne pas être trop vigoureux en essayant de mettre en place des tests pour du code stable. Ce genre de chose a tendance à avoir un effet d'entraînement et peut devenir incontrôlable.

2
graham.reeds
Vaut-il la peine pour une solution existante en production?
Serait-il préférable d'ignorer les tests de ce projet et de l'ajouter dans une éventuelle réécriture future?
Quoi de plus bénéfique; passer quelques semaines à ajouter des tests ou quelques semaines à ajouter des fonctionnalités?
2
udo

Il est peu probable que vous ayez une couverture de test significative, vous devez donc être tactique sur l'endroit où vous ajoutez des tests:

  • Comme vous l'avez mentionné, lorsque vous trouvez un bogue, c'est le bon moment pour écrire un test (pour le reproduire), puis corriger le bogue. Si vous voyez que le test reproduit le bogue, vous pouvez être sûr que c'est un bon test. Étant donné qu'une si grande partie des bogues sont des régressions (50%?), Cela vaut presque toujours la peine d'écrire des tests de régression.
  • Lorsque vous plongez dans une zone de code pour la modifier, c'est le bon moment pour écrire des tests autour d'elle. Selon la nature du code, différents tests sont appropriés. Un bon ensemble de conseils se trouve ici .

OTOH, cela ne vaut pas la peine de rester assis à écrire des tests autour du code dont les gens sont satisfaits - surtout si personne ne va le modifier. Cela n'ajoute tout simplement pas de valeur (sauf peut-être la compréhension du comportement du système).

Bonne chance!

1
ndp

Je ne suis en aucun cas un expert chevronné du TDD, mais bien sûr, je dirais qu'il est extrêmement important de tester les unités autant que vous le pouvez. Comme le code est déjà en place, je commencerais par mettre en place une sorte d'automatisation des tests unitaires. J'utilise TeamCity pour exercer tous les tests de mes projets, et cela vous donne un bon résumé de la façon dont les composants ont fonctionné.

Avec cela en place, je passerais à ces composants de type logique métier vraiment critiques qui ne peuvent pas échouer. Dans mon cas, il y a des problèmes de trigométrie de base qui doivent être résolus pour diverses entrées, donc je teste le diable de ceux-ci. La raison pour laquelle je fais cela est que lorsque je brûle l'huile de minuit, il est très facile de perdre du temps à creuser dans des profondeurs de code qui n'ont vraiment pas besoin d'être touchées, car vous sachez qu'elles sont testées = pour toutes les entrées possibles (dans mon cas, il y a un nombre fini d'entrées).

Ok, alors maintenant, j'espère que vous vous sentez mieux avec ces pièces critiques. Au lieu de m'asseoir et de frapper tous les tests, je les attaquais à mesure qu'ils montaient. Si vous rencontrez un bogue qui est un vrai PITA à corriger, écrivez les tests unitaires et éliminez-les.

Il y a des cas où vous constaterez que les tests sont difficiles car vous ne pouvez pas instancier une classe particulière du test, vous devez donc vous en moquer. Oh, mais peut-être que vous ne pouvez pas vous moquer facilement parce que vous n'avez pas écrit sur une interface. Je prends ces scénarios "whoops" comme une opportunité pour implémenter ladite interface, parce que, eh bien, c'est une bonne chose.

À partir de là, je configurerais votre serveur de construction ou toute autre automatisation que vous avez en place avec un outil de couverture de code. Ils créent des graphiques à barres désagréables avec de grandes zones rouges où vous avez une mauvaise couverture. Maintenant, une couverture à 100% n'est pas votre objectif, et une couverture à 100% ne signifie pas nécessairement que votre code est à l'épreuve des balles, mais la barre rouge me motive définitivement lorsque j'ai du temps libre. :)

1
Dave

Je suggère de lire un brillant article par un ingénieur TopTal, qui explique pour commencer à ajouter des tests: il contient beaucoup de mathématiques , mais l'idée de base est:

1) Mesurer le couplage afférent (CA) de votre code (dans quelle mesure une classe est utilisée par d'autres classes, ce qui signifie que la casser causerait des dommages étendus)

2) Mesurez la complexité cyclomatique (CC) de votre code (complexité plus élevée = changement de rupture plus élevé)

Vous devez identifier les classes avec CA et CC élevés, c'est-à-dire avoir une fonction f (CA, CC) et les classes avec le les plus petites différences entre les deux mesures doivent avoir la priorité la plus élevée pour la couverture des tests.

Pourquoi? Parce qu'un CA élevé mais des classes CC très faibles sont très importants mais peu susceptibles de se casser. D'un autre côté, un CA bas mais un CC élevé risquent de se casser, mais causeront moins de dégâts. Vous voulez donc équilibrer.

1
Andrejs

Il y a tellement de bonnes réponses que je ne répéterai pas leur contenu. J'ai vérifié votre profil et il semble que vous soyez développeur C # .NET. Pour cette raison, j'ajoute une référence au projet Microsoft PEX et Moles qui peut vous aider à générer automatiquement des tests unitaires pour le code hérité. Je sais que la génération automatique n'est pas la meilleure façon mais au moins c'est la façon de commencer. Consultez cet article très intéressant du magazine MSDN sur l'utilisation de PEX pour le code hérité .

1
Ladislav Mrnka

Vous dites que vous ne voulez pas acheter un autre livre. Il suffit donc de lire l'article de Michael Feather sur travailler efficacement avec le code hérité . Alors achetez le livre :)

1
APC

Si j'étais à votre place, je prendrais probablement une approche de l'extérieur vers l'intérieur, en commençant par des tests fonctionnels qui exercent l'ensemble du système. J'essayerais de re-documenter les exigences du système en utilisant un langage de spécification BDD comme RSpec, puis j'écrirais des tests pour vérifier ces exigences en automatisant l'interface utilisateur.

Ensuite, je faisais un développement axé sur les défauts pour les bogues récemment découverts, j'écrivais des tests unitaires pour reproduire les problèmes et je travaillais sur les bogues jusqu'à ce que les tests réussissent.

Pour les nouvelles fonctionnalités, je m'en tiendrai à l'approche de l'extérieur vers l'intérieur: commencez avec les fonctionnalités documentées dans RSpec et vérifiées en automatisant l'interface utilisateur (qui échouera bien sûr au départ), puis ajoutez des tests unitaires plus précis au fur et à mesure que la mise en œuvre avance.

Je ne suis pas un expert du processus, mais d'après le peu d'expérience dont je dispose, je peux vous dire que le BDD via des tests d'interface utilisateur automatisés n'est pas facile, mais je pense que cela en vaut la chandelle, et produirait probablement le plus d'avantages dans votre cas.

1
Mhmmd

Ça dépend...
C'est génial d'avoir des tests unitaires mais vous devez considérer qui sont vos utilisateurs et ce qu'ils sont prêts à tolérer afin d'obtenir un produit plus exempt de bogues. Inévitablement, en refactorisant votre code qui n'a actuellement aucun test unitaire, vous introduirez des bogues et de nombreux utilisateurs auront du mal à comprendre que vous rendez le produit temporairement plus défectueux pour le rendre moins défectueux à long terme. En fin de compte, ce sont les utilisateurs qui auront le dernier mot ...

0
trendl