Cela fait un moment que j'essaie d'apprendre à écrire des tests unitaires pour mon code.
Au départ, j’ai commencé à faire du vrai TDD, où je n’écrivais aucun code tant que j’avais écrit un test d’échec en premier.
Cependant, j'ai récemment eu un problème épineux à résoudre qui impliquait beaucoup de code. Après avoir passé quelques bonnes semaines à écrire des tests, puis du code, j’en suis arrivée à la conclusion malheureuse que mon approche n’allait pas aboutir et que je devrais jeter deux semaines de travail et recommencer.
C'est une décision assez mauvaise à prendre lorsque vous venez d'écrire le code, mais lorsque vous avez également écrit plusieurs centaines de tests unitaires, il devient encore plus difficile émotionnellement de tout gâcher.
Je ne peux pas m'empêcher de penser que j'ai perdu 3 ou 4 jours d'effort à rédiger ces tests alors que j'aurais pu simplement rassembler le code pour la validation technique et ensuite les rédiger par la suite, une fois que j'étais satisfait de mon approche.
Comment les personnes qui pratiquent le TDD gèrent-elles correctement ces situations? Y a-t-il lieu de modifier les règles dans certains cas ou écrivez-vous toujours servilement d'abord les tests, même si ce code peut s'avérer inutile?
la source
Réponses:
Je pense qu'il y a deux problèmes ici. La première est que vous n'avez pas compris à l'avance que votre conception d'origine n'était peut-être pas la meilleure approche. Si vous aviez su cela à l'avance, vous auriez peut-être choisi de développer un ou deux prototypes à jeter , d'explorer les options de conception possibles et d'évaluer le moyen le plus prometteur à suivre. En prototypage, vous n’avez pas besoin d’écrire un code de qualité de production ni d’essais unitaires dans chaque recoin (ou pas du tout), car vous vous concentrez uniquement sur l’apprentissage, pas sur le polissage du code.
Maintenant, se rendre compte que vous avez besoin de prototypage et d'expériences plutôt que de commencer immédiatement le développement de code de production, n'est pas toujours facile, ni même toujours possible. Armés des connaissances que vous venez d'acquérir, vous pourrez peut-être reconnaître la nécessité du prototypage la prochaine fois. Ou peut-être pas. Mais au moins, vous savez maintenant que cette option doit être envisagée. Et ceci en soi est une connaissance importante.
L'autre problème est à mon humble avis avec votre perception. Nous faisons tous des erreurs, et il est si facile de voir rétrospectivement ce que nous aurions dû faire différemment. C'est comme ça que nous apprenons. Notez votre investissement dans les tests unitaires, car il vous faudra apprendre que le prototypage peut être important et surmontez-le. Efforcez-vous simplement de ne pas commettre deux fois la même erreur :-)
la source
Le but de TDD est qu’il vous oblige à écrire de petits incréments de code dans de petites fonctions , précisément pour éviter ce problème. Si vous avez passé des semaines à écrire du code sur un domaine et que chaque méthode utilitaire que vous avez écrite devient inutile lorsque vous repensez l'architecture, vos méthodes sont certainement beaucoup trop volumineuses au départ. (Oui, je sais que ce n'est pas vraiment réconfortant maintenant ...)
la source
Brooks a dit "prévoyez d'en jeter un, vous le ferez de toute façon". Il me semble que c'est ce que vous faites. Cela dit, vous devriez écrire vos tests unitaires pour tester une unité de code et non une grande bande de code. Ce sont des tests plus fonctionnels et devraient donc sur toute implémentation interne.
Par exemple, si je veux écrire un solveur PDE (équations aux dérivées partielles), j'écrirai quelques tests pour tenter de résoudre des problèmes que je peux résoudre mathématiquement. Ce sont mes premiers tests unitaires - lisez: les tests fonctionnels sont exécutés dans le cadre d’un framework xUnit. Celles-ci ne changeront pas en fonction de l'algorithme que j'utilise pour résoudre le PDE. Tout ce qui m'importe, c'est le résultat. Les deuxièmes tests unitaires se concentreront sur les fonctions utilisées pour coder l'algorithme et seraient donc spécifiques à cet algorithme - par exemple, Runge-Kutta. Si je découvrais que Runge-Kutta ne convient pas, j'aurais toujours ces tests de haut niveau (y compris ceux qui ont montré que Runge-Kutta n'était pas approprié). Ainsi, la deuxième itération aurait encore beaucoup des mêmes tests que la première.
Votre problème est peut-être de conception et pas nécessairement de code. Mais sans plus de détails, c'est difficile à dire.
la source
N'oubliez pas que le TDD est un processus itératif. Ecrivez un petit test (dans la plupart des cas, quelques lignes devraient suffire) et exécutez-le. Le test devrait échouer. Maintenant, travaillez directement sur votre source principale et essayez d'implémenter la fonctionnalité testée pour que le test réussisse. Maintenant, recommencez.
Vous ne devez pas essayer d’écrire tous les tests en une fois, car, comme vous l’avez remarqué, cela ne va pas aboutir. Cela réduit le risque de perdre votre temps à rédiger des tests inutilisés.
la source
Je pense que vous l'avez dit vous-même: vous n'étiez pas sûr de votre approche avant de commencer à écrire tous vos tests unitaires.
Ce que j’ai appris en comparant les projets TDD réels avec lesquels j’ai travaillé (pas beaucoup en réalité, mais seulement trois couvrant deux années de travail) avec ce que j’avais appris théoriquement, c’est que les tests automatisés! = Les tests unitaires exclusif).
En d'autres termes, le T dans TDD ne doit pas nécessairement comporter un U ... Il est automatisé, mais est moins un test unitaire (comme dans les classes et méthodes de test) qu'un test fonctionnel automatisé: il est au même niveau. de granularité fonctionnelle selon l’architecture sur laquelle vous travaillez actuellement. Vous commencez de haut niveau, avec peu de tests et seulement une vue d'ensemble fonctionnelle, et vous finissez par vous retrouver avec des milliers d'UT et toutes vos classes bien définies dans une belle architecture ...
Les tests unitaires vous apportent une aide précieuse lorsque vous travaillez en équipe, pour éviter les modifications de code générant des cycles de bogues sans fin. Mais je n'ai jamais rien écrit d'aussi précis lorsque je commence à travailler sur un projet, avant d'avoir au moins un POC de travail global pour chaque user story.
Peut-être que c'est juste ma façon personnelle de le faire. Je n'ai pas l'expérience suffisante pour décider à partir de rien les structures ou la structure de mon projet, je ne perdrai donc pas mon temps à écrire des centaines d'UT depuis le début ...
Plus généralement, l’idée de tout casser et de tout jeter sera toujours là. Aussi «continus» que nous pouvons essayer d’être avec nos outils et méthodes, la seule façon de lutter contre l’entropie est parfois de tout recommencer. Mais le but est que, lorsque cela se produira, les tests automatisés et les tests unitaires que vous avez mis en œuvre rendront votre projet déjà moins coûteux que s’il n’y en avait pas - et il le fera si vous trouvez l’équilibre.
la source
La fusion des tests unitaires avec le développement piloté par les tests est la source de beaucoup d'angoisse et de malheurs. Revenons donc une fois de plus:
En résumé: les tests unitaires sont axés sur la mise en œuvre, TDD sur les exigences. Ce n'est pas la même chose.
la source
Le développement piloté par les tests est destiné à guider votre développement. Les tests que vous écrivez vous permettent d’affirmer l’exactitude du code que vous écrivez et d’accroître la vitesse de développement à partir de la première ligne.
Vous semblez croire que les tests sont un fardeau et ne sont conçus que pour un développement progressif plus tard. Cette ligne de pensée ne correspond pas à la TDD.
Peut-être pouvez-vous le comparer au typage statique: bien que vous puissiez écrire du code sans information de type statique, l'ajout de type statique au code aide à affirmer certaines propriétés du code, libérant ainsi l'esprit et permettant de se concentrer sur la structure importante, augmentant ainsi la vitesse et efficacité.
la source
Le problème avec une refactorisation majeure est que vous pouvez et allez parfois suivre un chemin qui vous amène à réaliser que vous avez piqué plus que vous ne pouvez en mâcher. Les refactorisations géantes sont une erreur. Si la conception du système est défectueuse au départ, la refactorisation peut vous mener loin avant que vous deviez prendre une décision difficile. Vous pouvez soit laisser le système en l'état et contourner le problème, soit planifier de revoir la conception et d'apporter des modifications majeures.
Il y a cependant une autre façon. Le véritable avantage du code de refactoring est de rendre les choses plus simples, plus lisibles et encore plus faciles à maintenir. Lorsque vous abordez un problème pour lequel vous avez des doutes, vous indiquez un changement, allez jusqu’à voir où il pourrait conduire afin d’en savoir plus sur le problème, puis jetez la pointe et appliquez une nouvelle refactorisation en fonction de la pointe. vous a appris. Le fait est que vous ne pouvez vraiment améliorer votre code avec certitude que si les étapes sont petites et si vos efforts de refactoring ne surchargent pas votre capacité à écrire vos tests en premier. La tentation est d'écrire un test, puis du code, puis du code supplémentaire, car une solution peut sembler évidente, mais vous vous rendez vite compte que votre modification modifiera de nombreux autres tests. Vous devez donc faire attention à ne changer qu'une seule chose à la fois.
La solution est donc de ne jamais faire de votre refactoring un projet majeur. Pas de bébé. Commencez par extraire les méthodes, puis cherchez à supprimer les doublons. Passez ensuite à l'extraction des classes. Chacune par petites étapes, un changement mineur à la fois. SI vous extrayez du code, écrivez d'abord un test. Si vous supprimez du code, supprimez-le, lancez vos tests et déterminez si l'un des tests interrompus sera nécessaire. Un petit pas de bébé à la fois. Il semble que cela prendra plus de temps, mais réduira considérablement votre temps de refactoring.
La réalité est cependant que chaque pointe représente apparemment un gaspillage d’efforts. Les modifications de code ne vont parfois nulle part, et vous vous retrouvez en train de restaurer votre code depuis votre vcs. Ceci est juste une réalité de ce que nous faisons au jour le jour. Chaque pointe qui échoue n'est pas perdue cependant, si elle vous apprend quelque chose. Chaque effort de refactoring qui échoue vous apprendra que vous essayez de faire trop, trop vite ou que votre approche est peut-être fausse. Cela aussi n’est pas une perte de temps si vous en tirez des leçons. Plus vous en ferez, plus vous en apprendrez et plus vous deviendrez efficace. Mon conseil est de simplement le porter pour le moment, d'apprendre à faire plus en faisant moins et d'accepter que c'est exactement ce qu'il faut faire jusqu'à ce que vous sachiez mieux jusqu'où prendre une pointe avant qu'elle ne vous mène nulle part.
la source
Je ne suis pas sûr de la raison pour laquelle votre approche s'est révélée imparfaite après 3 jours. En fonction de vos incertitudes dans votre architecture, vous pouvez envisager de modifier votre stratégie de test:
Si vous avez des doutes sur les performances, vous souhaitez peut-être commencer par quelques tests d’intégration permettant d’affirmer les performances?
Lorsque vous étudiez la complexité de l'API, écrivez de véritables tests unitaires simples et petits pour déterminer quelle serait la meilleure façon de procéder. Ne vous embêtez pas dans la mise en œuvre, demandez simplement à vos classes de renvoyer des valeurs codées en dur ou de leur donner des exceptions NotImplementedExceptions.
la source
Pour moi, les tests unitaires sont également une occasion de mettre l'interface sous une utilisation "réelle" (enfin, aussi réels que soient les tests unitaires!).
Si je suis obligé de mettre en place un test, je dois exercer mon design. Cela aide à garder les choses saines (si quelque chose est si complexe que le test est un fardeau, qu'en sera-t-il de l'utiliser?).
Cela n'évite pas les modifications de conception, mais en expose le besoin. Oui, une réécriture complète est une douleur. Pour (essayer de) l'éviter, j'ai l'habitude de mettre en place un (plusieurs) prototype (s), éventuellement en Python (avec le développement final en c ++).
Certes, vous n'avez pas toujours le temps pour tous ces cadeaux. Ce sont précisément les cas où vous aurez besoin d' un LARGER de temps pour atteindre vos objectifs ... et / ou de tout garder sous contrôle.
la source
Bienvenue au cirque des développeurs créatifs .
Au lieu de respecter tous les codes «légaux / raisonnables» de code au début,
essayez l’ intuition , surtout si elle est importante et nouvelle pour vous et si aucun échantillon n’a l’air de vouloir:
- Écrivez avec votre instinct, à partir de choses que vous connaissez déjà , pas avec votre mental et votre imagination.
- Et arrêtez.
- Prenez une loupe et inspectez tous les mots que vous écrivez: Vous écrivez "texte" car "texte" est proche de String, mais "verbe", "adjectif" ou quelque chose de plus précis est nécessaire, relisez et ajustez la méthode avec un nouveau sens
. .. ou vous avez écrit un morceau de code en pensant au futur? supprimez-le
- Corrigez, effectuez une autre tâche (sport, culture ou autre chose en dehors de l'entreprise), revenez et relisez.
- Tout va bien,
- Corrigez, faites une autre tâche, revenez et relisez.
- Tout va bien, passez à TDD
- Maintenant, tout est correct, bon
- Essayez le point de repère pour indiquer les choses à optimiser, faites-le.
Ce qui apparaît:
- vous avez écrit un code respectant toutes les règles
- vous obtenez une expérience, une nouvelle façon de travailler,
- quelque chose change dans votre esprit, vous n’aurez pas peur de la nouvelle configuration.
Et maintenant, si vous voyez un fichier UML ressemblant à ce qui précède, vous pourrez dire:
"Boss, je commence par TDD pour cela ...."
est-ce une autre chose nouvelle?
"Boss, je voudrais essayer quelque chose avant de décider de la façon dont je vais coder."
Cordialement, PARIS
Claude
la source