web-dev-qa-db-fra.com

TDD vs productivité

Dans mon projet actuel (un jeu, en C++), j'ai décidé d'utiliser 100% de développement piloté par les tests pendant le développement.

En termes de qualité du code, cela a été formidable. Mon code n'a jamais été aussi bien conçu ni exempt de bogues. Je ne grince pas les dents lorsque je regarde le code que j'ai écrit il y a un an au début du projet, et j'ai acquis une bien meilleure idée de la façon de structurer les choses, non seulement pour être plus facilement testable, mais pour être plus simple à mettre en œuvre et à utiliser. .

Cependant ... cela fait un an que j'ai commencé le projet. Certes, je ne peux y travailler que pendant mon temps libre, mais TDD me ralentit encore considérablement par rapport à ce à quoi je suis habitué. J'ai lu que la vitesse de développement plus lente s'améliore avec le temps, et je pense vraiment à des tests beaucoup plus facilement qu'auparavant, mais j'y suis depuis un an maintenant et je travaille toujours à un rythme d'escargot.

Chaque fois que je pense à la prochaine étape qui a besoin de travail, je dois m'arrêter à chaque fois et réfléchir à la façon dont j'écrirais un test pour me permettre d'écrire le code réel. Je vais parfois rester bloqué pendant des heures, sachant exactement quel code je veux écrire, mais ne sachant pas comment le décomposer assez finement pour le couvrir complètement avec des tests. D'autres fois, je vais rapidement imaginer une douzaine de tests et passer une heure à écrire des tests pour couvrir un petit morceau de code réel qui aurait sinon pris quelques minutes à écrire.

Ou, après avoir terminé le 50e test pour couvrir une entité particulière dans le jeu et tous les aspects de sa création et de son utilisation, je regarde ma liste de tâches et je vois la prochaine entité à coder, et je grimace d'horreur à l'idée d'écrire 50 autres tests similaires pour le mettre en œuvre.

Il est arrivé au point que, en examinant les progrès de l'année dernière, j'envisage d'abandonner TDD pour "terminer le fichu projet". Cependant, abandonner la qualité du code qui l'accompagne n'est pas quelque chose que j'attends avec impatience. J'ai peur que si j'arrête d'écrire des tests, alors je vais perdre l'habitude de rendre le code si modulaire et testable.

Suis-je peut-être en train de faire quelque chose de mal pour être si lent à ce sujet? Existe-t-il des alternatives qui accélèrent la productivité sans perdre complètement les avantages? TAD? Moins de couverture de test? Comment les autres survivent-ils au TDD sans tuer toute productivité et motivation?

136
Nairou

Permettez-moi de commencer par vous remercier de partager votre expérience et d'exprimer vos préoccupations ... qui, je dois le dire, ne sont pas rares.

  • Temps/Productivité: Écrire des tests est plus lent que de ne pas écrire de tests. Si vous l'étendez à cela, je serais d'accord. Cependant, si vous exécutez un effort parallèle où vous appliquez une approche non-TDD, il est probable que le temps que vous passez à casser-détecter-déboguer-et-corriger le code existant vous mettra dans le net négatif. Pour moi, TDD est le plus rapide que je puisse faire sans compromettre ma confiance en code. Si vous trouvez des choses dans votre méthode qui n'apportent pas de valeur ajoutée, éliminez-les.
  • Nombre de tests: si vous codez N choses, vous devez tester N choses. pour paraphraser une des lignes de Kent Beck "Testez uniquement si vous voulez que cela fonctionne."
  • Rester bloqué pendant des heures: moi aussi (parfois et pas> 20 minutes avant d'arrêter la ligne) .. C'est juste votre code qui vous dit que la conception a besoin de travail. Un test est juste un autre client pour votre classe SUT. Si un test a du mal à utiliser votre type, il en sera de même pour vos clients de production.
  • Tedium de tests similaires: Cela nécessite un peu plus de contexte pour moi d'écrire un contre-argument. Cela dit, arrêtez-vous et pensez à la similitude. Pouvez-vous piloter ces tests d'une manière ou d'une autre? Est-il possible d'écrire des tests sur un type de base? Il vous suffit ensuite d'exécuter le même ensemble de tests pour chaque dérivation. Écoutez vos tests. Soyez le bon type de paresseux et voyez si vous pouvez trouver un moyen d'éviter l'ennui.
  • Arrêter de penser à ce que vous devez faire ensuite (le test/la spécification) n'est pas une mauvaise chose. Au contraire, il est recommandé de construire "la bonne chose". Habituellement, si je ne peux pas penser à la tester, je ne pense pas non plus à l'implémentation. C'est une bonne idée de supprimer les idées d'implémentation jusqu'à ce que vous y arriviez. Peut-être qu'une solution plus simple est éclipsée par une conception préventive YAGNI-ish.

Et cela m'amène à la dernière question: comment puis-je m'améliorer? Ma (ou une) réponse est lire, réfléchir et pratiquer.

par exemple. Dernièrement, je garde un œil sur

  • si mon rythme reflète RG [Ref] RG [Ref] RG [Ref] ou est-ce RRRRGRRef.
  • % de temps passé dans l'état d'erreur Red/Compile.
  • Suis-je coincé dans un état de build rouge/cassé?
78
Gishu

Vous n'avez pas besoin d'une couverture de test à 100%. Soyez pragmatique.

34
Alistair

TDD me ralentit encore considérablement

C'est en fait faux.

Sans TDD, vous passez quelques semaines à écrire du code qui fonctionne principalement et passez l'année suivante à "tester" et à corriger de nombreux bugs (mais pas tous).

Avec TDD, vous passez un an à écrire du code qui fonctionne réellement. Ensuite, vous effectuez des tests d'intégration finaux pendant quelques semaines.

Le temps écoulé sera probablement le même. Le logiciel TDD sera de bien meilleure qualité.

23
S.Lott

Ou, après avoir terminé le 50e test pour couvrir une entité particulière dans le jeu et tous les aspects de sa création et de son utilisation, je regarde ma liste de tâches et je vois la prochaine entité à coder, et je grimace d'horreur à l'idée d'écrire 50 autres tests similaires pour le mettre en œuvre.

Cela me fait me demander combien vous suivez l'étape "Refactor" de TDD.

Lorsque tous vos tests sont réussis, il est temps pour vous de refactoriser le code et de supprimer la duplication. Bien que les gens s'en souviennent généralement, ils oublient parfois qu'il est également temps de refactoriser leurs tests , pour supprimer les doublons et simplifier les choses.

Si vous avez deux entités qui fusionnent en une seule pour permettre la réutilisation du code, envisagez également de fusionner leurs tests. Vous n'avez vraiment besoin que de tester les différences incrémentales dans votre code. Si vous n'effectuez pas régulièrement de maintenance sur vos tests, ils peuvent rapidement devenir lourds.

Quelques points philosophiques sur TDD qui pourraient être utiles:

  • Lorsque vous ne pouvez pas comprendre comment écrire un test, malgré une vaste expérience dans l'écriture de tests, c'est définitivement une odeur de code . Votre code manque en quelque sorte de modularité, ce qui rend difficile l'écriture de petits tests simples.
  • Ajouter un peu de code est parfaitement acceptable lorsque vous utilisez TDD. Écrivez le code que vous voulez, pour avoir une idée de ce à quoi il ressemble, puis supprimez le code et commencer par des tests.
  • Je vois la pratique du TDD extrêmement strict comme une forme d'exercice. Lorsque vous commencez pour la première fois, écrivez d'abord un test à chaque fois et écrivez le code le plus simple pour réussir le test avant de continuer. Cependant, cela n'est pas nécessaire une fois que vous êtes devenu plus à l'aise avec la pratique. Je n'ai pas de test unitaire pour chaque chemin de code possible que j'écris, mais grâce à l'expérience, je peux choisir ce qui doit être testé avec un test unitaire et ce qui peut être couvert par des tests fonctionnels ou d'intégration à la place. Si vous pratiquez le TDD de manière stricte depuis un an, j'imagine que vous êtes également proche de ce point.

EDIT: Sur le thème de la philosophie des tests unitaires, je pense que cela pourrait être intéressant pour vous de lire: The Way of Testivus =

Et un point plus pratique, sinon nécessairement très utile:

  • Vous mentionnez C++ comme langage de développement. J'ai beaucoup pratiqué le TDD en Java, en utilisant d'excellentes bibliothèques comme JUnit et Mockito. Cependant, j'ai trouvé que TDD en C++ était très frustrant en raison du manque de bibliothèques (en particulier, de cadres de simulation) disponibles. Bien que ce point ne vous aide pas beaucoup dans votre situation actuelle, j'espère que vous en tiendrez compte avant d'abandonner complètement le TDD.
20
jaustin

Question très intéressante.

Ce qui est important à noter, c'est que C++ n'est pas très facilement testable, et le jeu, en général, est également un très mauvais candidat pour TDD. Vous ne pouvez pas tester si OpenGL/DirectX dessine facilement le triangle rouge avec le pilote X et jaune avec le pilote Y. Si le vecteur normal de la carte de relief n'est pas inversé après les transformations de shader. Vous ne pouvez pas non plus tester les problèmes d'écrêtage sur les versions de pilote avec différentes précisions, etc. Le comportement de dessin indéfini en raison d'appels incorrects ne peut également être testé qu'avec une révision précise du code et un SDK à portée de main. Le son est également un mauvais candidat. Le multithreading, qui est encore très important pour les jeux, est à peu près inutile au test unitaire. C'est donc difficile.

Fondamentalement, le jeu est beaucoup de GUI, de son et de threads. L'interface graphique, même avec des composants standard auxquels vous pouvez envoyer WM_, est difficile à tester unitaire.

Donc, ce que vous pouvez tester, ce sont les classes de chargement de modèles, les classes de chargement de texture, les bibliothèques de matrices et autres, ce qui n'est pas beaucoup de code et, souvent, pas très réutilisable, si ce n'est que votre premier projet. De plus, ils sont emballés dans des formats propriétaires, il est donc peu probable que les entrées tierces puissent différer beaucoup, sauf si vous publiez des outils de modding, etc.

Là encore, je ne suis pas un gourou ou un évangéliste du TDD, alors prenez tout cela avec un grain de sel.

J'écrirais probablement quelques tests pour les principaux composants de base (par exemple la bibliothèque matricielle, la bibliothèque d'images). Ajoutez un tas de abort() sur les entrées inattendues dans chaque fonction. Et surtout, concentrez-vous sur du code résistant/résilient qui ne se casse pas facilement.

Concernant les erreurs hors tension, une utilisation intelligente de C++, RAII et une bonne conception contribuent grandement à les éviter.

En gros, vous avez beaucoup à faire juste pour couvrir les bases si vous voulez sortir le jeu. Je ne sais pas si TDD va aider.

10
Coder

Je suis d'accord avec les autres réponses, mais je veux également ajouter un point très important: refactoring des coûts !!

Avec des tests unitaires bien écrits, vous pouvez sans risque réécrire votre code. Tout d'abord, des tests unitaires bien écrits fournissent une excellente documentation de l'intention de votre code. Deuxièmement, tout effet secondaire malheureux de votre refactoring sera détecté dans la suite de tests existante. Ainsi, vous avez garanti que les hypothèses de votre ancien code sont également vraies pour votre nouveau code.

6
Morten

Comment les autres survivent-ils au TDD sans tuer toute productivité et motivation?

C'est complètement différent de mes expériences. Vous êtes soit incroyablement intelligent et écrivez du code sans bogues, (par exemple, désactivé par une erreur) ou vous ne réalisez pas que votre code a des bogues qui empêchent votre programme de fonctionner, et ne sont donc pas réellement terminés.

TDD, c'est avoir l'humilité de savoir que vous (et moi!) Faites des erreurs.

Pour moi, le temps d'écriture d'unittests est plus qu'économisé en temps de débogage réduit pour les projets qui sont effectués en utilisant TDD depuis le début.

Si vous ne faites pas d'erreurs, le TDD n'est peut-être pas aussi important pour vous qu'il l'est pour moi!

4
Tom

Je n'ai que quelques remarques:

  1. Il semble que vous essayiez de tester tout. Vous ne devriez probablement pas, juste les cas à haut risque et Edge d'un morceau de code/méthode particulier. Je suis presque sûr que la règle des 80/20 s'applique ici: vous passez 80% à écrire des tests pour les 20% de votre code ou cas qui ne sont pas couverts.

  2. Prioriser. Entrez dans le développement de logiciels agiles et faites une liste de ce que vous devez vraiment vraiment faire pour sortir en un mois. Relâchez ensuite, juste comme ça. Cela vous fera réfléchir à la priorité des fonctionnalités. Oui, ce serait cool si votre personnage pouvait faire un backflip, mais a-t-il valeur commerciale?

TDD est bon, mais seulement si vous ne visez pas une couverture de test à 100%, et ce n'est pas si cela vous empêche de produire une valeur commerciale réelle (c'est-à-dire des fonctionnalités, des choses qui ajoutent quelque chose à votre jeu).

3
Cthulhu

Oui, l'écriture de tests et de code peut prendre plus de temps que la simple écriture de code - mais l'écriture de code et les tests unitaires associés (à l'aide de TDD) sont beaucoup plus prévisibles que l'écriture de code et le débogage.

Le débogage est presque éliminé lors de l'utilisation de TDD - ce qui rend tous les processus de développement beaucoup plus prévisibles et au final - sans doute plus rapides.

Refactorisation constante - il est impossible d'effectuer une refactorisation sérieuse sans une suite complète de tests unitaires. Le moyen le plus efficace de construire ce filet de sécurité basé sur les tests unitaires est pendant le TDD. Un code bien refactorisé améliore considérablement la productivité globale du concepteur/de l'équipe qui gère le code.

1
ratkok

Envisagez de restreindre la portée de votre jeu et placez-le là où quelqu'un peut y jouer ou vous le libérez. Maintenir vos normes de test sans avoir à attendre trop longtemps pour sortir votre jeu pourrait être un compromis pour vous garder motivé. Les commentaires de vos utilisateurs peuvent offrir des avantages à long terme et vos tests vous permettent de vous sentir à l'aise avec les ajouts et les modifications.

0
JeffO