TDD vs. Productivité

131

Dans mon projet actuel (un jeu, en C ++), j'ai décidé d'utiliser Test Driven Development 100% pendant le développement.

En termes de qualité de code, cela a été formidable. Mon code n'a jamais été aussi bien conçu ni aussi sans bug. Je ne grince pas quand je lis le code que j'ai écrit il y a un an au début du projet et j'ai maintenant beaucoup mieux compris comment structurer les choses, non seulement pour être plus facilement testable, mais pour être plus simple à implémenter 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 le TDD me ralentit encore considérablement par rapport à ce que je suis habitué. J'ai lu que la vitesse de développement plus lente s'améliorait avec le temps, et je pense vraiment aux tests beaucoup plus facilement qu'auparavant, mais je le fais depuis un an et je travaille toujours au pas de l'escargot.

Chaque fois que je pense à la prochaine étape qui nécessite du travail, je dois m'arrêter à chaque fois et réfléchir à la manière dont j'écrirais un test pour lui permettre d'écrire le code lui-même. Je suis parfois bloqué pendant des heures, sachant exactement quel code je veux écrire, mais ne sachant pas le décomposer assez finement pour le couvrir complètement avec des tests. D'autres fois, j'imagine rapidement une douzaine de tests et passe une heure à rédiger des tests pour couvrir un minuscule morceau de code qui, autrement, aurait pris quelques minutes à écrire.

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

Nous en sommes arrivés au point où, considérant les progrès de l’année écoulée, j’envisage d’abandonner le TDD dans le but de "terminer ce foutu projet". Cependant, abandonner la qualité du code qui l'accompagne n'est pas quelque chose que je suis impatient de voir. Je crains que si j'arrête d'écrire des tests, alors je ne prendrai pas l'habitude de rendre le code aussi modulaire et testable.

Suis-je peut-être en train de faire quelque chose de mal pour être toujours aussi lent à ça? 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 personnes survivent-elles au TDD sans nuire à la productivité et à la motivation?

Nairou
la source
@Nairou: Vous pouvez toujours essayer de "terminer le projet"! Faire une branche maintenant. Il suffit d'écrire le code ici. Mais limitez ce que vous faites, en fonction du temps ou du nombre d’entités du jeu, et voyez si vous êtes allé plus vite. Vous pouvez ensuite ignorer cette branche, revenir au tronc et à TDD à partir de là et voir quelle est la différence.
Quamrana
9
Pour moi, écrire des tests trop tôt, c'est comme optimiser trop tôt. Vous travaillerez peut-être durement sur le test du code que vous supprimerez de toute façon dans le futur.
LennyProgrammers
Je suis un peu préoccupé par le fait que vous passez des heures à chercher un moyen de concevoir votre code de manière à ce qu'il soit plus testable. La testabilité est un attribut probable d'une conception bien pondérée, mais ce ne devrait pas être l'objectif primordial de la conception .
Jeremy
2
À l'époque où j'apprenais, nous avions un truc pour fournir des documents de conception. Nous avons d'abord écrit le code, puis les documents décrivant le code. Peut-être avez-vous besoin d'apprendre une quantité modérée de ce pragmatisme pour votre TDD. Si vous avez déjà un plan en tête, il est peut-être préférable de l'intégrer au code avant de passer les tests. Quels que soient les idéaux suggérés, il est parfois préférable de faire ce que vous êtes déjà prêt à faire, plutôt que de vous laisser distraire par autre chose, puis de revenir quand vous n'êtes plus frais.
Steve314
4
Je vais aller à l'encontre de l'opinion populaire et dire que TDD n'est peut-être pas toujours le bon choix si vous créez des jeux. Étant donné que quelqu'un de gamedev.stackexchange a déjà répondu à cette question de façon spectaculaire, je vais juste relier ceci ici .
l46kok

Réponses:

77

Permettez-moi de commencer par vous remercier de partager votre expérience et d'exprimer vos préoccupations ... ce que je dois dire, ce n'est pas inhabituel.

  • Temps / Productivité: L'écriture de tests est plus lente que de ne pas écrire de tests. Si vous envisagez cela, je suis d'accord. Toutefois, si vous exécutez un effort parallèle dans lequel vous appliquez une approche non-TDD, il est probable que le temps que vous passerez à détecter, déboguer et corriger le code existant vous mettra dans le négatif net. Pour moi, TDD est le plus rapide que je puisse faire sans compromettre mon code-confiance. Si vous trouvez des éléments dans votre méthode qui n’ajoutent pas de valeur, éliminez-les.
  • Nombre de tests: Si vous codez N éléments, vous devez tester N éléments. pour paraphraser une des lignes de Kent Beck " Ne testez que si vous voulez que cela fonctionne. "
  • Rester bloqué pendant des heures: moi aussi (parfois et pas plus de 20 minutes avant d'arrêter la ligne). C'est simplement votre code qui vous indique 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, vos clients en production le seront aussi.
  • Essais similaires: cela nécessite un peu plus de contexte pour rédiger un contre-argument. Cela dit, arrêtez-vous et réfléchissez à la similitude. Pouvez-vous piloter ces tests en quelque sorte? Est-il possible d'écrire des tests sur un type de base? Ensuite, il vous suffit 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.
  • S'arrêter pour réfléchir à ce que vous devez faire ensuite (le test / spécifications) n'est pas une mauvaise chose. Au contraire, il est recommandé de construire "la bonne chose". Généralement, si je ne vois pas comment le tester, je ne peux pas non plus penser à la mise en oeuvre. 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 de YAGNI-ish.

Et cela m'amène à la dernière question: comment puis-je aller mieux? Ma réponse (ou réponse) est Lecture, réflexion et pratique.

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 Rouge / Erreur de compilation.
  • Suis-je coincé dans un état de construction Red / Broken?
Gishu
la source
1
Je suis très curieux de savoir ce que vous pensez des données pilotant les tests. Voulez-vous simplement dire un seul ensemble de tests qui traitent des données externes (comme celles d'un fichier) plutôt que de réessayer un code similaire? Dans mon jeu, j'ai plusieurs entités, et chacune est très différente, mais il y a des choses communes qui sont faites avec elles (les sérialiser sur le réseau, s'assurer qu'elles ne soient pas envoyées à des joueurs inexistants, etc.) Jusqu'à présent, je n'ai pas trouvé de moyen de consolider cette situation, de même que des ensembles de tests pour chaque test presque identiques, qui diffèrent uniquement par l'entité qu'ils utilisent et les données qu'elle contient.
@Nairoi - Vous ne savez pas quel testeur vous utilisez. Je viens d'apprendre un nom pour ce que je voulais transmettre. Modèle de montage abstrait [ goo.gl/dWp3k] . Cela nécessite néanmoins que vous écriviez autant de projecteurs qu'il existe de types SUT concrets. Si vous voulez être encore plus concis, regardez la documentation de votre coureur. Par exemple, NUnit prend en charge les montages de test paramétrés et génériques (maintenant que je l'ai recherché) goo.gl/c1eEQ Vous semblez être la chose dont vous avez besoin.
Gishu
Intéressant, je n'ai jamais entendu parler de fixtures abstraites. J'utilise actuellement UnitTest ++ qui a des fixtures, mais pas des abstraites. Ses fixtures sont très littérales, juste un moyen de consolider le code de test que vous auriez autrement répété dans chaque test, pour un groupe de tests donné.
@asgeo - ne peut pas éditer ce commentaire .. le lien a ramassé un crochet de fin
Gishu
+1 pour 'se coincer est le symptôme de la conception nécessite plus de travail', bien que .. que se passe-t-il lorsque vous êtes bloqué (comme moi) à la conception?
Lurscher
32

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

Alistair
la source
2
Si vous n'avez pas une couverture de test de 100%, vous n'avez pas une confiance de 100%.
Christopher Mahan
60
Vous n’avez pas 100% de confiance, même avec 100% de la couverture du test. C'est le test 101. Les tests ne peuvent pas démontrer que le code est sans défaut; au contraire, ils ne peuvent que démontrer qu'il contient des défauts.
CesarGon
7
Pour ce que ça vaut, l'un des plus fervents défenseurs de TDD, Bob Martin, ne recommande pas une couverture à 100% - blog.objectmentor.com/articles/2009/01/31/… . Dans le secteur de la fabrication (acquis, différent du logiciel à bien des égards), personne ne privilégie la confiance à 100% car il peut consacrer une fraction de ses efforts à une confiance de 99%.
Chance
De plus (au moins la dernière fois que j'ai vérifié les outils dont nous disposons), les rapports de couverture de code indiquent si les lignes ont été exécutées mais n'incluent pas la couverture de valeur. comme il y avait une ligne semblable à a = x + yet bien que toutes les lignes du code aient été exécutées lors de tests, les tests n'ont été testés que dans le cas où y = 0, de sorte que le bogue (il aurait dû l'être a = x - y) n'a jamais été trouvé dans les tests.
Pete Kirkham
@Chance - J'ai lu le livre de Robert Martin "Clean coder ...", un nom long. Dans ce livre, il était dit que les tests devaient couvrir à 100% asymptotiquement, ce qui est proche de 100%. Et le lien de blog ne fonctionne plus.
Darius.V
22

TDD me ralentit encore considérablement

C'est en fait faux.

Sans TDD, vous passez quelques semaines à écrire du code qui fonctionne généralement et passez l'année suivante à "tester" et à corriger beaucoup (mais pas tous) des bogues.

Avec TDD, vous passez une année à écrire du code qui fonctionne réellement. Vous effectuez ensuite les tests d'intégration finaux pendant quelques semaines.

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

S.Lott
la source
6
Alors pourquoi ai-je besoin de TDD? "Le temps écoulé est le même"
21
@ Peter Long: Qualité du code. L'année "test" est la façon dont nous finissons avec un logiciel de merde qui fonctionne principalement.
S.Lott
1
@ Peter, tu plaisantes. La qualité de la solution TDD sera de loin supérieure.
Mark Thomas
7
Pourquoi ai-je besoin de TDD? Kent Beck mentionne la tranquillité d'esprit comme une préoccupation majeure, et c'est très convaincant pour moi. Je vis dans la peur constante de casser des choses lorsque je travaille sur du code sans tests unitaires.
7
@ Peter Long: "Le temps écoulé est le même" ... et vous pouvez à tout moment livrer un code fonctionnel .
Frank Shearar le
20

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

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

Lorsque tous vos tests sont réussis, il est temps de refactoriser le code et de supprimer les doublons. Bien que les gens se souviennent généralement de cela, ils oublient parfois qu'il est également temps de refactoriser leurs tests , de supprimer les doublons et de 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émentielles dans votre code. Si vous n'effectuez pas régulièrement la maintenance de vos tests, ceux-ci peuvent rapidement devenir difficiles à manier.

Quelques points philosophiques sur le TDD qui pourraient être utiles:

  • Lorsque vous ne savez pas comment écrire un test, malgré une longue expérience de la rédaction de tests, alors c'est définitivement une odeur de code . Votre code manque en quelque sorte de modularité, ce qui rend difficile la rédaction de petits tests simples.
  • L'utilisation d'un code TDD est parfaitement acceptable. Écrivez le code que vous voulez, pour avoir une idée de quoi il a l'air, puis supprimez le code et commencez par les tests.
  • Je considère la pratique extrêmement stricte du TDD comme une forme d’exercice. Lorsque vous débutez, commencez par écrire un test à chaque fois et écrivez le code le plus simple pour réussir le test avant de continuer. Cependant, ce 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 à mon expérience, je suis en mesure de choisir ce qui doit être testé avec un test unitaire et ce qui peut être couvert par des tests fonctionnels ou d'intégration. Si vous pratiquez le TDD de manière stricte depuis un an, j'imagine que vous êtes également proche de ce point.

EDIT: Au sujet de la philosophie des tests unitaires, je pense que cela pourrait vous intéresser 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 telles que JUnit et Mockito. Cependant, j'ai trouvé TDD en C ++ très frustrant en raison du manque de bibliothèques (en particulier de cadres moqueurs) 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.
jaustin
la source
4
Les tests de refactoring sont dangereux. Personne ne semble en parler, mais ça l'est. Je n'ai certainement pas de tests unitaires pour tester mes tests unitaires. Lorsque vous refactorisez pour réduire les doublons, vous augmentez généralement la complexité (car votre code devient plus général). Cela signifie qu'il est plus probable qu'il y ait un bug dans vos tests .
Scott Whitlock
2
Je ne suis pas d'accord pour dire que les tests de refactorisation sont dangereux. Vous ne refacturez que lorsque tout passe, alors si vous effectuez une refactorisation et que tout est encore vert, alors tout va bien. Si vous pensez que vous devez écrire des tests pour vos tests, j’estime que vous devez écrire des tests plus simples.
jaustin
1
C ++ est difficile à tester (le langage ne permet pas facilement de faire des choses qui rendent les simulacres faciles) J'ai remarqué que les fonctions qui sont des "fonctions" (ne fonctionnent que sur des arguments, les résultats apparaissent sous forme de valeurs de retour / paramètres) sont beaucoup plus faciles à tester que les "procédures" (retour nul, sans argument). J'ai constaté qu'il peut être plus facile de tester un code C modulaire bien conçu qu'un code C ++. Vous n'êtes pas obligé d'écrire en C, mais vous pouvez suivre l'exemple du C modulaire. Cela semble complètement fou, mais j'ai mis des tests unitaires sur du "mauvais C" où tout était global et très facile - tout l'état est toujours disponible !.
Anon
2
Je pense que c'est vraiment vrai. Je fais beaucoup de RedGreenRedGreenRedGreen (ou, le plus souvent, RedRedRedGreenGreenGreen), mais je réfléchis rarement. Mes tests n’ont certainement jamais été refondus, car j’ai toujours pensé qu’ils perdraient encore plus de temps à ne pas coder. Mais je peux voir en quoi cela pourrait être la cause des problèmes auxquels je suis confronté. Il est temps pour moi de réfléchir sérieusement à la refactorisation et à la consolidation.
Nairou
1
Cadre moqueur Google C ++ (intégré à la version de test de Google C ++) - bibliothèque moqueuse très puissante - souple, riche en fonctionnalités - tout à fait comparable à tout autre cadre moqueur.
ratkok
9

Question très intéressante.

Ce qui est important à noter, c’est que le C ++ n’est pas très facile à tester et que le jeu, en général, est également un très mauvais candidat pour le TDD. Vous ne pouvez pas tester si OpenGL / DirectX dessine facilement un triangle rouge avec le pilote X et jaune avec le pilote Y. Si le vecteur normal de la texture en relief n'est pas inversé après la transformation du shader. Vous ne pouvez pas non plus tester les problèmes de découpage sur les versions de pilotes avec des précisions différentes, etc. Un comportement de dessin non défini en raison d'appels incorrects ne peut également être testé qu'avec une vérification précise du code et du SDK. Le son est également un mauvais candidat. Le multithreading, qui est encore une fois assez important pour les jeux, est pratiquement inutile pour les tests unitaires. Donc c'est dur.

Fondamentalement, le jeu comporte beaucoup d’interfaces graphiques, de sons et de threads. L'interface graphique, même avec des composants standard auxquels vous pouvez envoyer WM_, est difficile à tester.

Donc, ce que vous pouvez tester, ce sont les classes de chargement de modèle, les classes de chargement de texture, les bibliothèques de matrices et autres, ce qui n’est pas beaucoup de code et, bien souvent, pas très réutilisable, si ce n’est que votre premier projet. En outre, ils sont regroupés dans des formats propriétaires. Il est donc peu probable que les entrées de tierces parties diffèrent beaucoup, sauf si vous publiez des outils de modification, etc.

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

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

En ce qui concerne les erreurs off-one, l'utilisation intelligente de C ++, RAII et d'une bonne conception est un moyen efficace de les éviter.

En gros, il vous reste beaucoup à faire pour couvrir les bases si vous voulez sortir le jeu. Je ne suis pas sûr si TDD aidera.

Codeur
la source
3
+1 J'aime beaucoup le concept de TDD et je l’utilise partout où je peux, mais vous soulevez un point très important sur lequel les partisans de TDD sont curieusement silencieux. Comme vous l'avez indiqué, il existe de nombreux types de programmation pour lesquels la rédaction de tests unitaires significatifs est extrêmement difficile, voire impossible. Utilisez TDD lorsque cela vous semble judicieux, mais certains types de code sont mieux développés et testés d’autres manières.
Mark Heath
@Mark: Eh bien, personne ne semble se soucier des tests d'intégration de nos jours, pensant que, grâce à une suite de tests automatisés, tout fonctionnera comme par magie lorsqu'il est mis en place et testé avec des données réelles.
gbjbaanb
D'accord avec cela. Merci pour une réponse pragmatique qui ne prescrit pas de manière dogmatique le TDD comme la réponse à tout, au lieu de ce qu’il est, qui est juste un autre outil de la boîte à outils du développeur.
JB
6

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

Avec des tests unitaires bien écrits, vous pouvez réécrire votre code en toute sécurité. Premièrement, des tests unitaires bien rédigés fournissent une excellente documentation sur l'intention de votre code. Deuxièmement, tous les effets secondaires malheureux de votre refactoring seront pris en compte par la suite de tests existante. Ainsi, vous avez garanti que les suppositions de votre ancien code sont également vraies pour votre nouveau code.

Morten
la source
4

Comment les autres personnes survivent-elles au TDD sans nuire à la productivité et à la motivation?

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

TDD consiste à avoir l’humilité de savoir que vous (et moi!) Commettez des erreurs.

Pour moi, le temps d'écriture avec unittests est bien plus qu'un gain de temps de débogage réduit pour les projets réalisés depuis le début avec TDD.

Si vous ne faites pas d'erreur, alors peut-être que TDD n'est pas aussi important pour vous que pour moi!

À M
la source
Eh bien, vous avez aussi des bugs dans votre code TDD;)
Coder
C'est vrai! mais ils tendent à être un type de bogue différent, si TDD est fait correctement. Je suppose que dire que le code doit être 100% exempt de bogues pour être fini n'est pas correct. Bien que si l' on définit un bug comme un écart par rapport au comportement défini test unitaire, alors je suppose que c'est gratuit :) bug
Tom
3

Je n'ai que quelques remarques:

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

  2. Prioriser. Lancez-vous dans le développement logiciel agile et dressez une liste de ce que vous devez vraiment faire pour sortir dans un mois. Puis relâchez, juste comme ça. Cela vous fera penser à la priorité des fonctionnalités. Ouais, ce serait cool si votre personnage pouvait faire un backflip, mais a-t-il une valeur commerciale ?

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

Cthulhu
la source
1

Oui, l'écriture de tests et de code peut prendre plus de temps que d'écrire du code - mais écrire du code et des tests unitaires associés (à l'aide de TDD) est beaucoup plus prévisible que d'écrire du code et de le déboguer.

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 finalement plus rapides - sans doute.

Refactorisation constante - il est impossible de procéder à 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 la période de TDD. Un code bien refactorisé améliore de manière significative la productivité globale du concepteur / de l’équipe qui gère le code.

Ratkok
la source
0

Pensez à réduire la portée de votre jeu et à le placer là où quelqu'un peut le jouer ou le relâcher. Maintenir vos normes de test sans attendre trop longtemps avant de publier votre jeu pourrait être un moyen terme pour rester 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.

JeffO
la source