Comment faire du développement piloté par les tests

15

J'ai seulement 2+ années d'expérience dans le développement d'applications. Au cours de ces deux années, mon approche du développement a été la suivante

  1. Analyser les exigences
  2. Composant / Objets Identity Core, Fonctions requises, Comportement, Processus et leurs contraintes
  3. Créer des classes, relation entre elles, contraintes sur le comportement et les états des objets
  4. Créer des fonctions, traiter avec des contraintes comportementales selon les exigences
  5. Tester l'application manuellement
  6. Si les modifications des exigences modifient le composant / les fonctions, testez l'application manuellement

Récemment, j'ai été initié à TDD et je pense que c'est un très bon moyen de faire du développement car le code développé a de bonnes raisons d'exister et de nombreux problèmes de post-déploiement sont atténués.

Mais mon problème est que je ne suis pas en mesure de créer des tests en premier, j'identifie plutôt les composants et j'écris simplement des tests pour eux avant d'écrire des composants. Ma question est

  1. Suis-je en train de faire c'est bien? Si ce n'est pas exactement ce que je dois changer
  2. Existe-t-il un moyen d'identifier si le test que vous avez écrit est suffisant?
  3. Est-ce une bonne pratique d'écrire un test pour une fonctionnalité très simple qui pourrait être équivalente à 1 + 1 = 2 ou est-ce juste une surexposition?
  4. Est-il bon de changer la fonctionnalité et de tester en conséquence si les exigences changent?
Yogesh
la source
2
"J'identifie les composants et j'écris juste un test pour eux avant d'écrire réellement les composants.": Je trouve cela correct: vous identifiez d'abord l'architecture grossière de votre système, puis commencez à coder. Pendant le codage (TDD), vous déterminez les détails des composants individuels et découvrez éventuellement des problèmes avec votre architecture que vous pouvez résoudre en cours de route. Mais je trouve OK que vous ne commencez pas à coder sans aucune analyse préalable.
Giorgio
Vous pouvez également envisager d'effectuer des tests unitaires / d'intégration automatisés sans effectuer TDD. Les deux sont souvent confondus, mais ce n'est pas la même chose.
Andres F.

Réponses:

19

Suis-je en train de faire c'est bien? Si ce n'est pas exactement ce que je dois changer

C'est difficile à dire juste à partir de cette courte description, mais je soupçonne que non, vous ne le faites pas correctement. Remarque: Je ne dis pas que ce que vous faites ne fonctionne pas ou est en quelque sorte mauvais, mais vous ne faites pas TDD. Le milieu "D" signifie "Driven", les tests pilotent tout, le processus de développement, le code, la conception, l'architecture, tout .

Les tests vous disent quoi écrire, quand l'écrire, quoi écrire ensuite, quand arrêter d'écrire. Ils vous disent le design et l'architecture. (La conception et l'architecture émergent du code par le refactoring.) TDD n'est pas une question de test. Il ne s'agit même pas d'écrire des tests d'abord: TDD consiste à laisser les tests vous guider, les écrire d'abord n'est qu'une condition préalable nécessaire pour cela.

Peu importe que vous écriviez réellement le code ou que vous le développiez complètement: vous écrivez (des squelettes de) code dans votre tête, puis écrivez des tests pour ce code. Ce n'est pas TDD.

Abandonner cette habitude est difficile . Vraiment, vraiment dur. Cela semble être particulièrement difficile pour les programmeurs expérimentés.

Keith Braithwaite a créé un exercice qu'il appelle TDD comme si vous le vouliez . Il consiste en un ensemble de règles (basées sur les trois règles de l'oncle Bob Martin sur le TDD , mais beaucoup plus strictes) que vous devez suivre strictement et qui sont conçues pour vous orienter vers une application plus rigoureuse du TDD. Cela fonctionne mieux avec la programmation en binôme (pour que votre binôme s'assure que vous ne violez pas les règles) et un instructeur.

Les règles sont les suivantes:

  1. Écrivez exactement un nouveau test, le plus petit test possible qui semble pointer dans la direction d'une solution
  2. Voyez-le échouer; les échecs de compilation comptent comme des échecs
  3. Faites passer le test à partir de (1) en écrivant le moins de code d'implémentation possible dans la méthode de test .
  4. Refactoriser pour supprimer la duplication et, au besoin, pour améliorer la conception. Soyez strict quant à l'utilisation de ces mouvements:
    1. vous voulez une nouvelle méthode - attendez le temps de refactoring, puis ... créez de nouvelles méthodes (non-test) en faisant l'une de ces méthodes, et en aucune autre manière:
      • préféré: faire Extraire la méthode sur le code d'implémentation créé selon (3) pour créer une nouvelle méthode dans la classe de test, ou
      • si vous devez: déplacer le code d'implémentation selon (3) dans une méthode d'implémentation existante
    2. vous voulez une nouvelle classe - attendez le temps de refactoring, puis ... créez des classes non-test pour fournir une destination pour une méthode de déplacement et pour aucune autre raison
    3. remplir les classes d'implémentation avec des méthodes en effectuant la méthode Move, et pas d'autre moyen

Typiquement, cela conduira à des conceptions très différentes de la "méthode pseudo-TDD" souvent utilisée pour "imaginer dans votre tête ce que devrait être la conception, puis écrire des tests pour forcer cette conception, implémenter la conception que vous aviez déjà envisagée avant d'écrire votre tests ".

Lorsqu'un groupe de personnes implémente quelque chose comme un jeu de tic tac toe utilisant un pseudo-TDD, ils se retrouvent généralement avec des conceptions très similaires impliquant une sorte de Boardclasse avec un tableau 3 × 3 de Integers. Et au moins une partie des programmeurs auront en fait écrit cette classe sans tests car ils "savent qu'ils en auront besoin" ou "auront besoin de quelque chose pour écrire leurs tests". Cependant, lorsque vous forcez ce même groupe à appliquer TDD comme si vous le vouliez, ils se retrouveront souvent avec une grande diversité de conceptions très différentes, n'utilisant souvent rien de même à distance similaire à a Board.

Existe-t-il un moyen d'identifier si le test que vous avez écrit est suffisant?

Quand ils couvrent tous les besoins de l'entreprise. Les tests sont un codage des exigences du système.

Est-ce une bonne pratique d'écrire un test pour une fonctionnalité très simple qui pourrait être équivalente à 1 + 1 = 2 ou est-ce juste une surexposition?

Encore une fois, vous l'avez à l'envers: vous n'écrivez pas de tests de fonctionnalité. Vous écrivez des fonctionnalités pour les tests. Si la fonctionnalité permettant de réussir le test s'avère insignifiante, tant mieux! Vous venez de remplir une exigence système et n'avez même pas eu à travailler dur pour cela!

Est-il bon de changer la fonctionnalité et de tester en conséquence si les exigences changent?

Non. L'inverse. Si une exigence change, vous modifiez le test qui correspond à cette exigence, regardez-le échouer, puis changez le code pour le faire passer. Les tests viennent toujours en premier.

C'est difficile à faire. Vous avez besoin de dizaines, peut-être de centaines d'heures de pratique délibérée pour construire une sorte de "mémoire musculaire" pour arriver à un point où, lorsque la date limite approche et que vous êtes sous pression, vous n'avez même pas besoin d'y penser , et cela devient le moyen de travail le plus rapide et le plus naturel.

Jörg W Mittag
la source
1
Une réponse très claire en effet! Du point de vue pratique, un cadre de test flexible et puissant est très agréable lors de la pratique du TDD. Bien qu'indépendante de TDD, la possibilité d'exécuter automatiquement des tests est inestimable pour déboguer une application. Pour commencer avec TDD, les programmes non interarctifs (style UNIX) sont probablement les plus faciles, car un cas d'utilisation peut être testé en comparant l'état de sortie et la sortie du programme à ce qui est attendu. Un exemple concret de cette approche peut être trouvé dans ma bibliothèque d' essence pour OCaml.
Michael Le Barbier Grünewald
4
Vous dites "lorsque vous forcez ce même groupe à appliquer TDD comme si vous le vouliez, ils se retrouveront souvent avec une grande diversité de conceptions très différentes, n'utilisant souvent rien de même à distance similaire à un conseil d'administration" comme si c'était une bonne chose . Il n'est pas du tout clair pour moi que c'est une bonne chose, et peut même être mauvais du point de vue de la maintenance, car il semble que la mise en œuvre serait très contre-intuitive pour quelqu'un de nouveau. Pourriez-vous expliquer pourquoi cette diversité d'implémentation est une bonne chose, ou du moins pas mauvaise?
Jim Clay
3
+1 La réponse est bonne en ce qu'elle décrit correctement TDD. Cependant, il montre également pourquoi TDD est une méthodologie défectueuse: une réflexion approfondie et une conception explicite sont nécessaires, en particulier face à des problèmes algorithmiques. Faire du TDD "à l'aveugle" (comme le prescrit TDD) en faisant semblant de ne pas avoir de connaissance du domaine conduit à des difficultés inutiles et à des impasses. Voir l'infâme débâcle du solveur Sudoku (version courte: TDD ne peut pas battre la connaissance du domaine).
Andres F.
1
@AndresF .: En fait, le blog auquel vous avez lié semble faire écho aux expériences que Keith a faites en faisant TDD comme si vous le vouliez: lorsque vous faites du "pseudo-TDD" pour Tic-Tac-Toe, ils commencent par créer une Boardclasse avec un Tableau 3x3 de ints (ou quelque chose comme ça). Alors que si vous les forcez à faire TDDAIYMI, ils finissent souvent par créer une mini-DSL pour capturer les connaissances du domaine. C'est juste anecdotique, bien sûr. Une étude statistiquement et scientifiquement valable serait bien, mais comme c'est souvent le cas avec des études comme celle-ci, elles sont soit trop petites, soit beaucoup trop chères.
Jörg W Mittag
@ JörgWMittag Corrigez-moi si je vous ai mal compris, mais dites-vous que Ron Jeffries faisait du "pseudo-TDD"? N'est-ce pas là une forme de sophisme «pas de véritable écossais»? (Je suis d'accord avec vous sur la nécessité d'études scientifiques supplémentaires; le blog auquel j'ai lié n'est qu'une anecdote colorée sur l'échec spectaculaire d'une instance spécifique d'utilisation du TDD. Malheureusement, il semble que les évangélistes du TDD soient trop bruyants pour le reste d'entre nous d'avoir une véritable analyse de cette métholodie et de ses prétendus avantages).
Andres F.
5

Vous décrivez votre approche de développement comme un processus «de haut en bas uniquement» - vous partez d'un niveau d'abstraction plus élevé et allez de plus en plus dans les détails. Le TDD, du moins sous sa forme courante, est une technique "ascendante". Et pour quelqu'un qui travaille principalement «de haut en bas», il peut en effet être très inhabituel de travailler «de bas en haut».

Alors, comment pouvez-vous apporter plus de "TDD" dans votre processus de développement? Tout d'abord, je suppose que votre processus de développement réel n'est pas toujours aussi «descendant» que vous l'avez décrit ci-dessus. Après l'étape 2, vous aurez probablement identifié certains composants qui sont indépendants des autres composants. Parfois, vous décidez de mettre en œuvre ces composants en premier. Les détails de l'API publique de ces composants ne suivent probablement pas seuls vos besoins, les détails suivent également vos décisions de conception. C'est le point où vous pouvez commencer avec TDD: imaginez comment vous allez utiliser le composant et comment vous allez réellement utiliser l'API. Et lorsque vous commencez à coder une telle utilisation de l'API sous forme de test, vous venez de commencer avec TDD.

Deuxièmement, vous pouvez faire du TDD même lorsque vous allez coder davantage "de haut en bas", en commençant par les composants qui dépendent d'abord d'autres composants non existants. Ce que vous devez apprendre, c'est d'abord comment "simuler" ces autres dépendances. Cela vous permettra de créer et de tester des composants de haut niveau avant de passer aux composants de niveau inférieur. Un exemple très détaillé sur la façon de faire TDD de haut en bas peut être trouvé dans ce blog de Ralf Westphal .

Doc Brown
la source
3

Suis-je en train de faire c'est bien? Si ce n'est pas exactement ce que je dois changer

Tu vas très bien.

Existe-t-il un moyen d'identifier si le test que vous avez écrit est suffisant?

Oui, utilisez un outil de couverture de test / code . Martin Fowler offre de bons conseils sur la couverture des tests.

Est-ce une bonne pratique d'écrire un test pour une fonctionnalité très simple qui pourrait être équivalente à 1 + 1 = 2 ou est-ce juste une surexposition?

En général, toute fonction, méthode, composant, etc. qui devrait donner des résultats compte tenu de certaines entrées est un bon candidat pour un test unitaire. Cependant, comme avec la plupart des choses dans la vie (d'ingénierie), vous devez considérer vos compromis: l'effort est-il compensé en écrivant le test unitaire résultant en une base de code plus stable à long terme? En général, choisissez d'abord d'écrire du code de test pour les fonctionnalités cruciales / critiques. Plus tard, si vous trouvez qu'il y a des bogues associés à une partie non testée du code, ajoutez d'autres tests.

Est-il bon de changer la fonctionnalité et de tester en conséquence si les exigences changent?

La bonne chose à propos des tests automatisés est que vous verrez immédiatement si un changement rompt les assertions précédentes. Si vous vous y attendez en raison des exigences modifiées, oui, il est correct de changer le code de test (en fait, en TDD pur, vous changeriez d'abord les tests en fonction des exigences, puis adopteriez le code jusqu'à ce qu'il réponde aux nouvelles exigences).

miraculixx
la source
La couverture du code peut ne pas être une mesure très fiable. L'application de% de la couverture entraîne généralement de nombreux tests non nécessaires (comme des tests pour tous les paramètres, des contrôles nuls, etc. - qui sont des tests pour le plaisir de tests qui n'ajoutent presque aucune valeur) et une perte de temps de développement, alors que le code est difficile à tester les chemins peuvent ne pas être testés du tout.
Paul
3

L'écriture des tests d'abord est une approche complètement différente de l'écriture de logiciels. Les tests ne sont pas seulement un outil de vérification de la fonctionnalité du code approprié (ils réussissent tous), mais la force qui définit la conception. Bien que la couverture des tests puisse être une mesure utile, elle ne doit pas être l'objectif en soi - l'objectif de TDD n'est pas d'atteindre un bon% de couverture de code, mais de réfléchir à la testabilité de votre code avant de l'écrire.

Si vous rencontrez des problèmes pour écrire des tests en premier, je vous recommande fortement de faire une session de programmation par paires avec une personne expérimentée en TDD, afin de vous familiariser avec "la façon de penser" de toute l'approche.

Une autre bonne chose à faire est de regarder une vidéo en ligne où un logiciel est développé en utilisant TDD dès la première ligne de celui-ci. Le bon que j'ai utilisé pour me présenter au TDD était Let's Play TDD de James Shore. Jetez un coup d'œil, il illustrera comment fonctionne la conception émergente, quelles questions devez-vous vous poser lors de l'écriture de tests et comment de nouvelles classes et méthodes sont créées, refactorisées et itérées.

Existe-t-il un moyen d'identifier si le test que vous avez écrit est suffisant?

Je pense que ce n'est pas la bonne question à poser. Lorsque vous faites du TDD, vous avez choisi de faire du TDD et de la conception émergente pour écrire des logiciels. Si une nouvelle fonctionnalité que vous devez ajouter commence toujours par un test, elle sera toujours là.

Est-ce une bonne pratique d'écrire un test pour une fonctionnalité très simple qui pourrait être équivalente à 1 + 1 = 2 ou est-ce juste une surexposition?

Évidemment, cela dépend, utilisez votre jugement. Je préfère ne pas écrire de tests sur les paramètres de contrôles nuls, si la méthode ne fait pas partie de l'API publique, mais sinon, pourquoi ne confirmeriez-vous pas que la méthode Add (a, b) renvoie effectivement a + b?

Est-il bon de changer la fonctionnalité et de tester en conséquence si les exigences changent?

Encore une fois, lorsque vous modifiez ou ajoutez de nouvelles fonctionnalités à votre code, vous commencez par un test, qu'il s'agisse d'ajouter un nouveau test ou d'en modifier un existant lorsque les exigences changent.

Paul
la source