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

140

J'envisage fortement d'ajouter des tests unitaires à un projet existant qui est en production. Cela a été commencé il y a 18 mois avant que je puisse vraiment voir les avantages du TDD (face palm) , donc maintenant c'est une solution assez volumineuse 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 que parfois un vieux bogue semble refaire surface, ou un bogue est enregistré comme corrigé sans vraiment être corrigé. Les tests unitaires réduiraient ou empêcheraient ces problèmes de se produire.

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

Existe-t-il des processus / étapes à suivre pour garantir qu'une solution existante est correctement testée à l'unité et pas seulement intégrée? Comment puis-je m'assurer que les tests sont de bonne qualité et ne sont pas simplement un cas de test, mieux que pas de tests .

Donc je suppose que ce que je demande aussi, c'est;

  • Cela en 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?
  • Ce qui sera 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 du fait que vous parliez à 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 non seulement confirment mes soupçons existants selon lesquels c'est une bonne chose à faire, mais aussi quelques 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 passer des heures de travail à déplacer le développement futur du produit vers TDD. Je veux aborder ce défi et développer mon raisonnement sans mon propre point de vue biaisé.

djdd87
la source
11
Lien obligatoire vers le livre de Michael Feathers sur le sujet: amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/…
Mark Rushakoff
1
@Mark - Merci, c'est dans le lien que j'ai fourni. J'espérais obtenir une réponse décente sans acheter un autre livre ... même si vous ne pouvez jamais avoir trop de livres (à moins que vous ne vouliez faire le travail, c'est vrai).
djdd87
1
Vous avez vraiment besoin de lire ce livre :) C'est l'un de mes favoris et aide vraiment à comprendre la tension entre refactoring et tests automatisés.
manuel aldana

Réponses:

177

J'ai introduit des tests unitaires dans des bases de code qui ne l'avaient pas auparavant. Le dernier grand projet auquel j'ai participé et 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 avons eu plus de 4500 tests donnant une couverture de code d'environ 33% dans une base de code avec 230 000 + production LOC (application financière Win-Forms 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 de la part des parties concerné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 encore 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 ne contient aucun test est également une compétence spécialisée en soi. À moins que vous ou quelqu'un de votre équipe n'ayez une expérience réussie de l'introduction de tests unitaires dans une base de code existante, je dirais que la lecture du 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 manque des tests fondamentaux qui auraient été inclus si j'avais utilisé TDD dès le départ.

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

La réponse au 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. À mesure que les niveaux de compétence s'améliorent, commencez à élever la barre pour tout le code nouvellement écrit ajouté à votre projet. Continuez à vous améliorer etc ...

Maintenant, en lisant entre les lignes ici, j'ai l'impression que cela vient de la mentalité de "la perfection comme excuse pour ne pas agir". Un meilleur état d'esprit est de se concentrer sur la confiance en soi. Donc, comme vous ne savez peut-être pas encore comment le faire, vous saurez comment faire au fur et à mesure et remplissez les blancs. Par conséquent, il n'y a aucune raison de s'inquiéter.

Encore une fois, c'est une compétence. Vous ne pouvez pas passer de zéro test à la perfection TDD en une seule approche de livre de cuisine «processus» ou «étape par étape» de manière linéaire. Ce sera un processus. Vos attentes doivent être de faire des progrès et des améliorations graduels et progressifs. Il n'y a pas de pilule magique.

La bonne nouvelle est qu'au fur et à mesure que les mois (et même les années) passent, votre code commencera progressivement à devenir un code «correct», 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 découpler le code, plutôt que d'écrire les tests unitaires eux-mêmes.

Existe-t-il des processus / étapes à suivre pour garantir qu'une solution existante est correctement testée à l'unité et pas seulement 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 couverture de code.

Formez votre personnel.

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

Utilisez la couverture de code comme référence pour savoir dans quelle mesure votre base de code de production est testée.

Le temps de construction doit toujours être RAPIDE. Si votre temps de construction est lent, vos compétences en tests unitaires 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 compilation en moins de 10 minutes (~ 1-quelques ms / test est une bonne ligne directrice mais très approximative, quelques exceptions peuvent s'appliquer comme le code utilisant la réflexion, etc. ).

Inspectez et adaptez-vous.

Comment puis-je m'assurer que les tests sont de bonne qualité et ne sont pas simplement un cas de test, 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 de faire appel à quelqu'un qui en a.

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

Cela en 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 personnes et les compétences ne devrait 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?

Il faudrait tenir compte non seulement de l'investissement en personnel et en compétences, mais surtout du coût total de possession et de 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.

Ce qui sera 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és.

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 nouvelles habitudes, à améliorer la discipline et la sensibilisation à la qualité, à mieux concevoir des 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 qu'elles doivent être pour que cette approche réussisse, donc arrêter les progrès pour passer tout le temps à ajouter beaucoup de tests ne fonctionnera tout simplement pas.

En outre, l'ajout de tests unitaires à une base de code existante de toute taille de projet importante est une entreprise de grande taille qui nécessite un engagement et de la 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 s'attendre à un retour sur investissement en interrompant le flux de valeur commerciale. Cela ne volera pas, et franchement cela ne devrait pas.

Troisièmement, vous souhaitez inculquer de solides valeurs d’orientation commerciale à votre équipe. La qualité n'est jamais au détriment du client et vous ne pouvez pas aller vite sans la qualité. De plus, le client vit dans un monde en mutation 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 répondant aux besoins en constante évolution de vos clients. 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. Cette dynamique positive est ce que vous devez viser car elle 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 cela pourra aider

Mahol25
la source
Reflète le même état d'esprit que moi. Actuellement, je suis en train de présenter ce processus / méthode exact pour introduire des cas de test dans une application JAVA Large. :)
Darshan Joshi
3
C'est la meilleure réponse articulée sur n'importe quel sujet que j'ai jamais lu sur Stackoverflow. Bien joué! Si vous ne l'avez pas fait, je vous exhorte à envisager d'écrire un livre sur votre réponse à la dernière question.
Yermo Lamers
Merci Yermo. Je ne sais pas si j'ai le temps d'écrire un livre. Mais peut-être pourrais-je écrire un article de blog ou deux. Je viens de commencer mon nouveau blog, donc ça peut prendre un certain temps.
Mahol25
2
Ce conseil de test unitaire dudes est un conseil de vie général. Sérieusement.
Wjdavis5
24
  • Cela en 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!

  • Ce qui sera 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) facilite grandement le fonctionnement du projet à l'avenir et réduit considérablement la probabilité que vous enverriez des problèmes stupides à l'utilisateur.

Tests à réaliser a priori sont ceux qui vérifient si vous pensez que l'interface publique de votre code (et chaque module qu'elle contient) fonctionne comme vous le pensez. 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, testez chaque bogue actuel dans votre base de données de bogues qui induit exactement le bogue et qui passera lorsque le bogue sera corrigé. Ensuite, corrigez ces bugs! :-)

L'ajout de tests coûte du temps au départ, mais vous êtes remboursé plusieurs fois au back-end car votre code finit par être de bien meilleure qualité. Cela compte énormément lorsque vous essayez d'expédier une nouvelle version ou d'effectuer une maintenance.

Boursiers Donal
la source
Merci pour la réponse élaborée. Confirmé ce que je ressentais. Je verrai quelles autres réponses seront publiées avant de voter et accepterons cependant.
djdd87
15

Le problème avec la mise à niveau des tests unitaires 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éécrivez le composant entier. Si vous avez le temps de le faire, vous vous construirez un bon filet de sécurité, mais vous auriez pu introduire des bugs subtils 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 y intégrer, à part une réécriture complète, ce qui ne peut généralement pas être justifié lorsque le code fonctionne et rapporte 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 soulevé, puis à conserver ces scripts sous la forme d'une suite de tests de régression pour des modifications ultérieures. De cette façon, vous pouvez au moins commencer à créer des tests pour l'application sans trop la modifier, cependant, il s'agit plus de tests de régression de bout en bout que de tests unitaires appropriés.

fletcher
la source
Si le code est assez bien divisé depuis le début, il est assez facile de le tester. Le problème survient lorsque vous avez un code qui est assemblé de manière désordonnée et où les seuls points de test exposés sont pour des tests d'intégration complète. (J'ai un code comme celui-là où la testabilité est presque nulle en raison du montant qu'elle dépend d'autres composants qui ne sont pas faciles à simuler, et où le déploiement nécessite un redémarrage du serveur. Testable ... mais difficile à bien faire.)
Donal Fellows
13

Je suis d'accord avec ce que presque tout le monde a 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, cela a un coût. Cela se fait au prix de ne pas créer de nouvelles fonctionnalités. La façon dont ces deux choses s'équilibrent 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? Années?
  • Pour qui écrivez-vous ce code? Des clients payants? Un professeur? Un projet open source?
  • À quoi ressemble ton horaire? Avez-vous des délais difficiles à respecter? Avez-vous des dates limites?

Encore une fois, permettez-moi de souligner, les tests sont précieux et vous devriez travailler pour mettre votre ancien code à l'essai. C'est vraiment plus une question de façon dont vous l'abordez. Si vous pouvez vous permettre de tout abandonner et de tester tout votre ancien code, faites-le. Si ce n'est pas réaliste, voici ce que vous devriez faire au moins

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

De plus, ce n'est pas une proposition du tout ou rien. Si vous avez une équipe de, disons, quatre personnes, et que vous pouvez respecter vos délais en confiant à une ou deux personnes une tâche de test héréditaire, faites-le.

Éditer:

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 passer des heures de travail à déplacer le développement futur du produit vers 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 les gens 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 disposer de tests automatisés d'une certaine forme pour tout projet de toute complexité. Non, les tests ne s'écrit pas d'eux-mêmes et, oui, il faudra un peu plus de temps pour faire sortir les choses. Mais à long terme, il faudra plus de temps et coûter plus cher pour corriger les bogues après coup que d'écrire des tests à l'avance. Période. C'est tout ce qu'on peut en dire.

Haydenmuhl
la source
9

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

Une des premières choses que nous avons faites (après avoir configuré un serveur de construction continue) a été d'ajouter des tests de régression. C'étaient des 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 de jeux de données de régression que nous conservons dans Subversion (dans un référentiel distinct de notre code, en raison de sa taille). FixtureSetUp de chaque test copie l'un de ces ensembles 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 .csv à un instantané enregistré. Ces instantanés .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 (auquel 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 (et lorsque vous corrigez ces bogues, les tests de régression échoueront).

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 harnais de test autour des rapports, nous avons pu obtenir un certain niveau de couverture sur une grande partie de notre base de code. L'écriture 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ère 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 corrigions des bogues, ou ajoutions des améliorations, ou devions comprendre du code. Les tests de régression ne suppriment en aucun cas le besoin de tests unitaires; ils sont juste un premier niveau filet de sécurité, de sorte que vous obtenez quelques - uns rapidement niveau de couverture de test. Ensuite, vous pouvez commencer la refactorisation pour rompre les dépendances, afin de pouvoir 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 casser. Mais au moins pour nous, ils en valaient vraiment la peine. Ils ont attrapé d'innombrables bogues au cours des cinq dernières années, et ils les attrapent en quelques heures, plutôt que d'attendre un cycle d'AQ. 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 y ajoutons même de temps en temps, car nous avons encore tellement de code que nos 6000 + les tests unitaires ne couvrent pas.

Joe White
la source
8

Ça vaut vraiment le coup. Notre application comporte 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é que cela prendrait une éternité pour le régler dans l'application (cela prend plusieurs minutes juste pour arriver au point où se trouvaient les problèmes). Je voulais introduire des tests unitaires automatisés et 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 éléments existants 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 voulez modifier quelque chose, vous devez couvrir cette fonctionnalité avec des tests afin que vous sachiez que vous avez effectué les modifications correctement. Et bien sûr, pour les nouvelles fonctionnalités, écrivez les tests qui échouent, puis implémentez la fonctionnalité manquante.

Hugh Brackett
la source
6

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

Cependant, vous devez garder à l'esprit certaines distinctions: boîte noire vs boîte blanche, et unité vs fonctionnelle. Étant donné que les définitions varient, voici ce que j'entends par là:

  • Boîte noire = tests qui sont écrits sans connaissance particulière de l'implémentation, qui se penchent généralement sur les cas extrêmes pour s'assurer que les choses se passent comme un utilisateur naïf s'y attendrait.
  • Boîte blanche = tests qui sont écrits avec la connaissance de l'implémentation, qui essaient souvent d'exercer des points de défaillance bien connus.
  • Tests unitaires = tests d'unités individuelles (fonctions, modules séparables, etc.). Par exemple: vous assurer que votre classe de tableau 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 choses du monde réel, fermer, terminer. J'aime faire une distinction entre ceux-ci 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 avais le plus pour mon argent grâce aux tests fonctionnels et en boîte blanche . 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 brise 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 de cohérence utile qui vous permet de vous assurer de ne jamais casser les 10 cas d'utilisation les plus courants.

Les tests de boîtes noires 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 de l'expédition, vous avez généralement trouvé (à la dure) la majorité des cas extrêmes et des problèmes que ces tests auraient découverts.

Comme les autres, je vous rappellerai également les deux choses les plus importantes à propos du TDD:

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

Le fait d'ajouter des tests unitaires à une application en 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, rappelez-vous 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 seconde moitié de votre deuxième question: mettez un point d'honneur à 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 : Développement de logiciels orientés objet guidés par des tests )

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

Une autre question est intégrée dans votre publication pour vous assurer que tout test rétro-ajusté est effectué correctement . La chose importante à s'assurer est que les tests unitaires sont vraiment des tests unitaires , et cela signifie (le plus souvent) que les tests de mise à niveau nécessitent de refactoriser le code existant pour permettre le découplage de vos couches / composants (cf. injection de dépendances; inversion de contrôle; stubbing; railleur). Si vous ne parvenez pas à appliquer cela, vos tests deviennent des tests d'intégration, qui sont utiles, mais moins ciblés et plus fragiles que les vrais tests unitaires.

Seb Rose
la source
4

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

  1. Dans un arbre source séparé, créez des tests de régression ou de `` fumée '', en utilisant un outil pour les générer, ce qui pourrait vous rapprocher de la couverture de 80%. Ces tests exécutent tous les chemins logiques du code et vérifient à partir de ce moment 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 les changements 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 effectuez des modifications (corrections de bogues ou fonctionnalités) via TDD, une fois terminé, il est probable que le test de fumée du compagnon échoue. Vérifiez que les échecs sont conformes aux attentes en raison des modifications apportées et supprimez le test de fumée le moins lisible, car votre test unitaire écrit à la main couvre entièrement ce composant amélioré. Assurez-vous que votre couverture de test ne diminue pas, reste la même ou augmente.

  5. Lorsque vous corrigez des bogues, écrivez un test unitaire échouant qui expose le bogue en premier.

Chris B
la source
3

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 se glissent dans la production.

Identifiez les domaines projets / modules où des 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 la correction de bogues.

Cela en vaut-il la peine pour une solution existante en production?

Oui. Vous verrez l'effet des bugs tomber et la maintenance devenir 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.

Ce qui sera plus bénéfique; passer quelques semaines à ajouter des tests ou quelques semaines à ajouter des fonctionnalités?

Vous posez la mauvaise question. Décidément, la fonctionnalité est plus importante que toute autre chose. Mais, vous devriez plutôt demander si passer quelques semaines à ajouter des tests rendra mon système plus stable. Cela aidera-t-il mon utilisateur final? Cela aidera-t-il un nouveau développeur dans l'équipe à comprendre le projet et aussi à s'assurer qu'il n'introduit pas de bogue par manque de compréhension de l'impact global d'un changement.

PK
la source
3

J'aime beaucoup Refactor the Low-Hanging Fruit en tant que réponse à la question de savoir par où commencer le refactoring. C'est un moyen de faciliter la création d'un meilleur design 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, selon vos besoins; écrire des tests pour le nouveau code; écrire des tests pour les bogues au fur et à mesure qu'ils apparaissent. Vous craignez de négliger les zones les plus difficiles à atteindre de la base de code, et c'est certainement un risque, mais pour commencer: commencez! Vous pouvez atténuer le risque sur la route 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, couvrant le nouveau code, couvrant le code que vous regardez , alors vous couvrez le code qui a le plus besoin de tests.

Carl Manaster
la source
2
  • Oui, ça l'est. lorsque vous commencez à ajouter de nouvelles fonctionnalités, cela peut entraîner des modifications de l'ancien code et, par conséquent, il s'agit d'une source de bogues potentiels.
  • (voir le premier) avant de commencer à ajouter de nouvelles fonctionnalités, tout le code (ou presque) devrait (idéalement) être couvert par des tests unitaires.
  • (voir le premier et le second) :). une nouvelle fonctionnalité grandiose peut "détruire" l'ancien code travaillé.
garik
la source
2

Oui, c'est possible: 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 un code stable. Ce genre de chose a tendance à avoir un effet d'entraînement et peut devenir incontrôlable.

graham.reeds
la source
2

Cela en vaut-il la peine pour une solution existante en production?
Oui. Mais vous n'avez pas besoin d'écrire tous les tests unitaires pour commencer. Ajoutez-les simplement un par un.

Serait-il préférable d'ignorer les tests de ce projet et de l'ajouter dans une éventuelle réécriture future?
Non. La première fois que vous ajoutez du code qui brise la fonctionnalité, vous le regretterez.

Ce qui sera plus bénéfique; passer quelques semaines à ajouter des tests ou quelques semaines à ajouter des fonctionnalités?
Pour les nouvelles fonctionnalités (code), c'est simple. Vous écrivez d'abord le test unitaire, puis la fonctionnalité. Pour l'ancien code, vous décidez du chemin. Vous n'êtes pas obligé d'avoir tous les tests unitaires en place ... Ajoutez ceux qui vous font le plus mal de ne pas avoir ... Le temps (et les erreurs) diront sur lequel vous devez vous concentrer;)

tu fais
la source
2

Mettre à jour

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

Je pense qu'il est logique d'ajouter des tests unitaires à tout 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 pas non plus de sens. Ajouter des tests au fur et à mesure que vous refactorisez / ajoutez des éléments est probablement la meilleure façon d'ajouter des tests et de rendre le code plus maintenable dans un projet existant sans tests.

Réponse précédente

Je vais lever quelques sourcils ici :)

Tout d'abord, quel est votre projet - s'il s'agit d'un compilateur ou d'un langage ou d'un framework ou de tout autre élément qui ne changera pas fonctionnellement pendant longtemps, alors je pense que c'est absolument fantastique d'ajouter des tests unitaires.

Cependant, si vous travaillez sur une application qui nécessitera probablement des modifications de fonctionnalités (en raison de l'évolution des exigences), il ne sert à rien de faire cet effort supplémentaire.

Pourquoi?

  1. Les tests unitaires couvrent uniquement 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'utilisabilité et tous les autres types de problèmes)

  2. Les tests unitaires coûtent du temps! Maintenant, d'où je viens, c'est un bien 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 pour les utilisateurs, ils vont demander des modifications - vous aurez donc des versions qui feront les choses mieux, plus rapidement et feront probablement de nouvelles choses - il peut également y avoir beaucoup de refactorisation à mesure que votre code se développe. Le maintien d'une suite de tests unitaires complète 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 voit l'utilisateur. Bien sûr, vos méthodes peuvent fonctionner exactement comme elles le faisaient le premier jour, l'interface entre la couche de présentation et la couche de gestion peut être parfaite - mais devinez quoi? L'utilisateur s'en fiche! Demandez à de vrais testeurs de tester votre application. Et le plus souvent, ces méthodes et interfaces doivent de toute façon changer, tôt ou tard.

Ce qui sera 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é, rédiger de meilleurs manuels d'aide, résoudre les bogues en attente, etc.

Maintenant, ne vous méprenez pas - Si vous êtes absolument convaincu que les choses ne vont pas changer pendant 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!

Roopesh Shenoy
la source
Et je vous exhorte à lire ceci - joelonsoftware.com/items/2009/01/31.html
Roopesh Shenoy
Préférez-vous «corriger les bogues en attente» ou les empêcher de se produire? Des tests unitaires appropriés permettent de gagner du temps en minimisant le temps consacré à la correction des bogues.
Ihor Kaharlichenko
C'est un mythe. Si vous me dites que les tests unitaires automatisés remplacent les tests manuels, vous vous trompez sérieusement. Et que consignent les testeurs manuels, sinon les bogues?
Roopesh Shenoy
Et oui, ne vous méprenez pas - je ne dis pas que les tests unitaires sont un gaspillage absolu - il s'agit de considérer le temps qu'il faut pour les écrire et les raisons pour lesquelles ils pourraient devoir changer lorsque vous changez de produit, est-ce qu'ils rapportent vraiment. Pour moi, j'ai essayé les deux côtés et la réponse a été non, ils ne remboursent pas assez vite.
Roopesh Shenoy
1

Il est peu probable que vous ayez jamais une couverture de test significative, vous devez donc être tactique quant à 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 pour corriger le bogue. Si vous voyez le test reproduire le bogue, vous pouvez être sûr que c'est un bon test non valide. Étant donné qu'une si grande partie des bogues sont des régressions (50%?), Il 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 de code dont les gens sont satisfaits, surtout si personne ne va le modifier. Cela n'ajoute tout simplement pas de valeur (sauf peut-être comprendre le comportement du système).

Bonne chance!

ndp
la source
1

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

APC
la source
1

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

Ensuite, je ferais du développement basé sur les défauts pour les bogues nouvellement 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 extérieure-intérieure: commencez par les fonctionnalités documentées dans RSpec et vérifiées en automatisant l'interface utilisateur (qui échouera bien sûr au début), puis ajoutez des tests unitaires plus fins au fur et à mesure que l'implémentation 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 BDD via des tests d'interface utilisateur automatisés n'est pas facile, mais je pense que cela en vaut la peine et que cela vous rapportera probablement le plus d'avantages dans votre cas.

Mhmmd
la source
1

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. Puisque le code est déjà en place, je commencerais par mettre en place une sorte d'automatisation des tests unitaires. J'utilise TeamCity pour effectuer tous les tests de mes projets, et cela vous donne un bon résumé de la façon dont les composants ont fonctionné.

Une fois 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 à fond ceux-ci. La raison pour laquelle je fais cela est que lorsque je brûle de l'huile de minuit, il est très facile de perdre du temps à creuser des profondeurs de code qui n'ont vraiment pas besoin d'être touchées, car vous savez qu'elles sont testées pour toutes les entrées possibles (dans mon cas, il y a un nombre fini d'entrées).

Ok, donc maintenant vous vous sentez mieux avec ces éléments critiques. Au lieu de m'asseoir et de lancer tous les tests, je les attaquerais au fur et à mesure qu'ils se présentaient. Si vous rencontrez un bogue qui est un vrai PITA à corriger, écrivez les tests unitaires pour cela 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 à partir du test, vous devez donc vous en moquer. Oh, mais peut-être que vous ne pouvez pas vous en moquer facilement parce que vous n'avez pas écrit sur une interface. Je prends ces scénarios "whoops" comme une opportunité d'implémenter ladite interface, car, eh bien, c'est une bonne chose.

À partir de là, je ferais configurer 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. Désormais, 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. :)

Dave
la source
1

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 un développeur C # .NET. Pour cette raison, j'ajoute une référence au projet Microsoft PEX and Moles qui peut vous aider avec des tests unitaires de génération automatique pour le code hérité. Je sais que l'autogénération 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é .

Ladislav Mrnka
la source
1

Je suggère de lire un article brillant d'un ingénieur TopTal, qui explique par commencer l'ajout de tests: il contient beaucoup de maths, mais l'idée de base est:

1) Mesurez le couplage afférent (CA) de votre code (combien une classe est utilisée par d'autres classes, ce qui signifie que sa rupture 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 un CA et un CC élevés, c'est-à-dire avoir une fonction f (CA, CC) et les classes avec les plus petites différences entre les deux métriques doivent avoir la priorité la plus élevée pour la couverture de test.

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

Andrejs
la source
0

Cela dépend ...
C'est formidable 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 pas de tests unitaires pour le moment, 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 ...

tendance
la source
-2

Oui. Non. Ajout de tests.

Adopter une approche plus TDD éclairera en fait mieux vos efforts pour ajouter de nouvelles fonctionnalités et facilitera grandement les tests de régression. Vérifiez-le!

indra
la source