Je ne comprends pas comment TDD m'aide à obtenir un bon design si j'ai besoin d'un design pour commencer à le tester.

50

J'essaie de comprendre TDD, en particulier la partie développement. J'ai consulté des livres, mais ceux que j'ai trouvés abordent principalement la partie relative aux tests: l'histoire de NUnit, pourquoi les tests sont bons, Red / Green / Refactor et la création d'un calculateur de cordes.

Bien, mais c'est "juste" des tests unitaires, pas TDD. Plus précisément, je ne comprends pas comment TDD m'aide à obtenir une bonne conception si j'ai besoin d'une conception pour commencer à la tester.

Pour illustrer, imaginez ces 3 exigences:

  • Un catalogue doit avoir une liste de produits
  • Le catalogue doit rappeler quels produits un utilisateur a vus
  • Les utilisateurs devraient pouvoir rechercher un produit

À ce stade, de nombreux livres tirent un lapin magique d'un chapeau et se plongent simplement dans «Test du produit», mais ils n'expliquent pas comment ils en sont venus à la conclusion qu'il existe un produit «ProductService». C’est la partie "Développement" de TDD que j’essaie de comprendre.

Il doit exister une conception existante, mais des éléments extérieurs aux services de l'entité (c'est-à-dire: il existe un produit, il doit donc exister un produitService) sont introuvables. Par exemple, la deuxième condition nécessite que Utilisateur, mais où puis-je mettre la fonctionnalité à rappeler? Et la fonction de recherche est-elle une fonctionnalité de ProductService ou un service de recherche distinct? Comment saurais-je lequel je devrais choisir?)

Selon SOLID , il me faudrait un UserService, mais si je conçois un système sans TDD, je risque de me retrouver avec toute une panoplie de services à méthode unique. Le TDD n’a-t-il pas pour but de me faire découvrir mon design?

Je suis un développeur .net, mais les ressources Java pourraient également fonctionner. Je pense qu'il ne semble pas exister de véritable exemple d'application ou de livre traitant d'une application métier réelle. Quelqu'un peut-il fournir un exemple clair illustrant le processus de création d'une conception à l'aide de TDD?

Michael Stum
la source
2
TDD n'est qu'une partie de la méthodologie de développement. Bien sûr, vous devrez utiliser une sorte de conception (soit directe, soit mieux évolutive) pour tout organiser.
Euphoric
3
@gnat: C'est une enquête pour savoir pourquoi les livres TDD ne rendent pas le processus de conception plus clair.
Robert Harvey
4
@gnat: C'était votre édition, pas la mienne. :) Voir ma modification du titre de la question et du corps.
Robert Harvey
9
Si vous avez lu le travail de Robert C. Martin ou peut-être regardé l'une de ses vidéos, vous verrez qu'il a souvent un dessin en tête, mais qu'il n'est pas marié. Il pense que sa conception préconçue du bon design va émerger de ses tests, mais il ne l’oblige pas. Et à la fin, parfois, ce design le fait, parfois pas. Mon point ici est que votre propre expérience antérieure vous guidera, mais que les tests devraient vous conduire. Les tests doivent pouvoir développer ou supprimer votre conception.
Anthony Pegram
3
Donc, il ne s'agit pas vraiment de tests, mais de design. Seulement, cela ne vous aide pas vraiment avec le design, mais vous aide à valider le design. Mais n'est-ce pas! @ # $ Ing testing?
Erik Reppen

Réponses:

17

L'idée du TDD est de commencer par les tests et de travailler à partir de cela. Ainsi, pour prendre votre exemple de "Un catalogue a besoin d'une liste de produits" peut être considéré comme ayant le test "Rechercher des produits dans le catalogue" et il s'agit donc du premier test. Maintenant, qu'est-ce qui tient un catalogue? Que tient un produit? Ce sont les pièces suivantes et l’idée est d’obtenir quelques éléments qui ressembleraient à un ProductService qui naîtrait de la réussite de ce premier test.

L’idée de TDD est de commencer par un test, puis d’écrire le code qui le fait passer comme premier point. Les tests unitaires font partie de cela, mais vous ne regardez pas l'image globale qui est formée en commençant par les tests, puis en écrivant le code afin qu'il n'y ait pas d'ignorance à ce stade car il n'y a pas encore de code.


Didacticiel de développement piloté par les tests, où les diapositives 20 à 22 sont les plus importantes. L'idée est de savoir ce que la fonctionnalité doit faire en conséquence, rédigez un test et construisez ensuite une solution. La conception dépendra de ce qui est requis, cela peut être simple ou non. Un point clé est d’utiliser TDD dès le départ plutôt que d’essayer d’introduire tardivement dans un projet. Si vous commencez d'abord par des tests, cela peut aider et vaut probablement la peine d'être noté dans un sens. Si vous essayez d'ajouter les tests plus tard, cela devient un élément qui peut être différé ou retardé. Les dernières diapositives peuvent également être utiles.


Le principal avantage de TDD est qu’en commençant par les tests, vous n’êtes pas bloqué dans une conception au départ. L’idée est donc de construire les tests et de créer le code qui passera ces tests en tant que méthodologie de développement. Un gros design en amont peut poser des problèmes car cela donne l’idée de verrouiller les choses en place, ce qui rend le système en cours de construction moins agile au final.


Robert Harvey a ajouté ceci dans les commentaires, ce qui mérite d'être mentionné dans la réponse:

Malheureusement, je pense qu'il s'agit d'une idée fausse commune à propos de TDD: vous ne pouvez pas développer une architecture logicielle en écrivant simplement des tests unitaires et en les faisant passer. La rédaction de tests unitaires influence le design, mais ne le crée pas . Tu dois faire ça.

JB King
la source
31
@MichaelStum: Malheureusement, je pense qu'il s'agit d'une idée fausse commune sur TDD: vous ne pouvez pas développer une architecture logicielle en écrivant simplement des tests unitaires et en les faisant passer. La rédaction de tests unitaires influence le design, mais ne le crée pas . Tu dois faire ça.
Robert Harvey
4
@RobertHarvey, JimmyHoffa: si je pouvais voter 100 fois sur vos commentaires, je le ferais!
Doc Brown
9
@ Robert Harvey: Je suis heureux que vous ayez écrit à propos de cette idée fausse commune: j'entends trop souvent dire qu'il faut juste s'asseoir et écrire toutes sortes de tests unitaires et un design "émergera" spontanément. Et si votre conception est mauvaise, c'est parce que vous n'avez pas écrit suffisamment de tests unitaires. Je conviens avec vous que les tests sont un outil permettant de spécifier et de vérifier les exigences de votre conception, mais que vous devez effectuer vous-même la conception. Je suis entièrement d'accord.
Giorgio
4
@Giorgo, RobertHarvey: +1000 à RobertHarvey de ma part également. Malheureusement, cette idée fausse est assez commune pour que certains praticiens "experts" de TDD / Agile le croient vraie. Comme par exemple, ils prétendent que vous pouvez "faire évoluer" un résolveur de sudoku en TDD, sans connaissance du domaine ni analyse d'aucune sorte . Je me demande si Ron Jeffries a déjà publié un suivi sur les limites du TDD ou expliqué pourquoi il a soudainement arrêté son expérience sans aucune conclusion ni leçons apprises.
Andres F.
3
@Andres F: Je connais l'histoire du sudoku et je pense que c'est très intéressant. Je pense que certains développeurs font l'erreur de penser qu'un outil (par exemple, TDD ou SCRUM) peuvent remplacer la connaissance du domaine et leurs propres efforts, et s'attendent à ce que, en appliquant mécaniquement une méthode particulière, un bon logiciel "émerge" comme par magie. Ce sont souvent des gens qui n'aiment pas passer trop de temps à l'analyse et à la conception et préfèrent coder quelque chose directement. Pour eux, suivre une méthodologie particulière est un alibi pour ne pas concevoir correctement. Mais c'est à mon humble avis un abus de TDD.
Giorgio
8

Pour ce que ça vaut, TDD m'aide à obtenir le meilleur design plus rapidement que ne pas le faire. Je viendrais probablement au meilleur design avec ou sans celui-ci. Mais le temps que j'aurais passé à y réfléchir et à essayer de comprendre le code revient à écrire des tests. Et c'est moins de temps. Pour moi. Pas pour tout le monde. Et, même si cela prenait le même temps, cela me laisserait une série de tests, ce qui rendrait le refactoring plus sûr, conduisant à un code encore meilleur sur toute la ligne.

Comment ça se passe?

Premièrement, cela m'encourage à penser à chaque classe en tant que service à un code client. Un meilleur code provient de la réflexion sur la manière dont le code appelant souhaite utiliser l'API plutôt que de se soucier de l'aspect du code lui-même.

Deuxièmement, cela m'empêche de rédiger une trop grande complexité cyclométique dans une seule méthode, pendant que j'y réfléchis. Chaque chemin supplémentaire à travers une méthode aura tendance à doubler le nombre de tests que je dois faire. La pure paresse dicte qu'après avoir ajouté trop de logique et que je dois écrire 16 tests pour ajouter une condition, il est temps d'en extraire une partie dans une autre méthode / classe et de la tester séparément.

C'est vraiment aussi simple que cela. Ce n'est pas un outil de conception magique.

pdr
la source
6

J'essaie de comprendre TDD ... Pour illustrer cela, imaginez ces 3 exigences:

  • Un catalogue doit avoir une liste de produits
  • Le catalogue doit rappeler quels produits un utilisateur a vus

Ces exigences devraient être reformulées en termes humains. Qui veut savoir quels produits l'utilisateur a déjà visionnés? L'utilisateur? Un vendeur?

  • Les utilisateurs devraient pouvoir rechercher un produit

Comment? De nom? Par marque? La première étape du développement piloté par les tests consiste à définir un test, par exemple:

browse to http://ourcompany.com
enter "cookie" in the product search box
page should show "chocolate-chip cookies" and "oatmeal cookies"

>

À ce stade, de nombreux livres tirent un lapin magique d'un chapeau et se plongent simplement dans «Test du produit», mais ils n'expliquent pas comment ils en sont venus à la conclusion qu'il existe un produit «ProductService».

Si ce sont les seules exigences, je ne ferais certainement pas un saut pour créer un service produit. Je pourrais créer une page Web très simple avec une liste de produits statique. Cela fonctionnerait parfaitement jusqu'à ce que vous obteniez les conditions requises pour ajouter et supprimer des produits. À ce stade, je pourrais décider qu'il est plus simple d'utiliser une base de données relationnelle et un ORM, et de créer une classe Product mappée sur une seule table. Toujours pas de ProductService. Des classes telles que ProductService seront créées quand et si elles sont nécessaires. Plusieurs requêtes Web doivent peut-être exécuter les mêmes requêtes ou mises à jour. Ensuite, la classe ProductService sera créée pour empêcher la duplication de code.

En résumé, TDD pilote le code à écrire. La conception se produit lorsque vous faites des choix d'implémentation, puis refactorisez le code en classes pour éliminer les doublons et contrôler les dépendances. Au fur et à mesure que vous ajoutez du code, vous devrez créer de nouvelles classes pour conserver le code SOLID. Mais vous n'avez pas besoin de décider à l'avance que vous aurez besoin d'une classe de produit et d'une classe de service de produit. Vous trouverez peut-être que la vie est parfaite avec juste une classe de produits.

Kevin Cline
la source
OK, non ProductServicealors. Mais comment TDD vous a-t-il dit qu'il vous fallait une base de données et un ORM?
Robert Harvey
4
@ Robert: ça n'a pas été le cas. C'est une décision de conception, basée sur mon jugement du moyen le plus efficace de répondre à l'exigence. Mais la décision pourrait changer.
Kevin Cline
1
Un bon design ne sera jamais produit comme effet secondaire d'un processus arbitraire. Avoir un système ou un modèle avec lequel travailler et encadrer les choses est génial, mais test-first-TDD, IMO crée un conflit d’intérêts en se vendant également comme une chose qui garantira que les gens ne seront pas mordus de manière inattendue par les effets secondaires de mauvais code qui n'aurait pas dû arriver en premier lieu. Le design requiert de la réflexion, de la conscience et de la prévoyance. Vous n'apprenez pas ceux de l'élagage des symptômes auto-découverts. Vous les apprenez en découvrant comment éviter les mauvaises branches mutantes.
Erik Reppen
Je pense que le test 'ajoute un produit; redémarrez l'ordinateur et redémarrez le système; le produit ajouté doit toujours être visible. ' indique d'où vient le besoin d'une sorte de base de données (mais cela pourrait toujours être un fichier plat ou XML).
Yatima2975
3

D'autres peuvent ne pas être d'accord, mais pour moi beaucoup de méthodologies plus récentes reposent sur l'hypothèse que le développeur va faire la plupart de ce que les méthodologies plus anciennes décrivaient juste par habitude ou par fierté personnelle, que le développeur fait habituellement quelque chose d'assez évident pour eux, et le travail est encapsulé dans un langage propre ou dans les parties propres d’un langage un peu brouillon afin que vous puissiez effectuer toutes les opérations de test.

Quelques exemples où je suis tombé sur cela dans le passé:

  • Prenez un groupe d’entrepreneurs spécialisés dans les travaux et leur dites que leur équipe est agile et test d’abord. Ils n'ont souvent pas l'habitude de travailler selon les spécifications et ils ne se soucient pas de la qualité du travail, tant qu'il dure assez longtemps pour que le projet soit terminé.

  • Essayez de faire quelque chose de nouveau test d'abord, passez une grande partie de votre temps à déchirer les tests lorsque vous trouvez que différentes approches et interfaces sont de la merde.

  • Codez quelque chose de bas niveau et soyez soit giflé pour le manque de couverture, soit écrivez un grand nombre de tests qui ne représentent pas une grande valeur, car vous ne pouvez pas vous moquer des comportements sous-jacents auxquels vous êtes lié.

  • Toute situation dans laquelle vous ne disposez pas à l'avance de suffisamment de mécanismes sous-jacents pour ajouter une fonctionnalité testable sans écrire une série de bits non testables sous-jacents, comme un sous-système de disque ou une interface de communication de niveau tcpip.

Si vous faites du TDD et que cela fonctionne pour votre bien, c'est bon pour vous, mais il y a beaucoup de choses (des tâches entières ou des étapes d'un projet) là où cela n'ajoute tout simplement pas de valeur.

Votre exemple sonne comme si vous n’êtes même pas encore parvenu à une conception. Vous devez donc avoir une conversation sur l’architecture ou créer un prototypage. À mon avis, vous devez d'abord en tenir compte.

Facture
la source
1

Je suis convaincu que TDD constitue une approche très précieuse pour la conception détaillée du système, à savoir les API et le modèle objet. Cependant, pour arriver à un point où vous commenceriez à utiliser TDD, vous devez avoir une vue d'ensemble du projet déjà modélisée d'une manière ou d'une autre et vous devez également avoir une vue d'ensemble de l'architecture telle qu'elle a déjà été modélisée. @ user414076 paraphrase Robert Martin comme ayant une idée de design en tête, sans être marié à celle-ci. Exactement. Conclusion - TDD n’est pas la seule activité de conception en cours, c’est la manière dont les détails de la conception sont précisés. Le TDD doit être précédé par d'autres activités de conception et s'intégrer dans une approche globale (telle que l'agilité) qui traite de la manière dont la conception globale est créée et évolue.

FYI - deux livres que je recommande sur le sujet et qui donnent des exemples concrets et réalistes:

Logiciel croissant orienté objet, guidé par des tests - explique et donne un exemple complet du projet. Ceci est un livre sur le design, pas les tests . Les tests servent à spécifier le comportement attendu lors des activités de conception.

Guide de développement - Guide de développement pratique - une procédure lente et pas à pas permettant de développer une application complète, bien que petite.

Chuck Krutsinger
la source
0

TTD entraîne la découverte de la conception par échec de test, et non par succès. Vous pouvez donc tester des inconnus et procéder à un nouveau test répétitif au fur et à mesure que les inconnus sont exposés, ce qui aboutit à un ensemble complet de tests unitaires. Retrofit après l'écriture / libération du code.

Par exemple, vous pouvez être obligé de saisir plusieurs formats différents, mais tous ne sont pas encore connus. En utilisant TDD, vous écrivez d’abord un test qui vérifie que la sortie appropriée est fournie, quel que soit le format d’entrée. Évidemment, ce test échouera. Vous devez donc écrire du code pour gérer les formats connus et effectuer un nouveau test. Comme les formats inconnus sont exposés lors de la collecte des exigences, de nouveaux tests sont écrits avant l' écriture du code, ils devraient également échouer. Ensuite, un nouveau code est écrit pour prendre en charge les nouveaux formats et tous les tests sont réexécutés, ce qui réduit les risques de régression.

Il est également utile de considérer l'échec de l'unité comme un code "non fini" au lieu d'un code "défectueux". TDD autorise les unités inachevées (défaillances attendues), mais réduit le nombre de défaillances d’unités (défaillances inattendues).

Daniel Pereira
la source
1
Je conviens qu'il s'agit d'un flux de travail valide, mais cela n'explique pas vraiment comment une architecture de haut niveau peut émerger d'un tel flux de travail.
Robert Harvey
1
C'est vrai, une architecture de haut niveau telle que le modèle MVC ne sortira pas uniquement de TDD. Cependant, ce qui peut émerger de TDD est un code conçu pour être facilement testable, ce qui est une considération de conception en soi.
Daniel Pereira
0

Dans la question, il est indiqué:

... beaucoup de livres sortent un lapin magique d'un chapeau et se plongent simplement dans "Test du produit", mais ils n'expliquent pas comment ils en sont venus à la conclusion qu'il existe un produit "produit".

Ils sont arrivés à cette conclusion en réfléchissant à la façon dont ils allaient tester ce produit. "Quel genre de produit fait ça?" "Eh bien, nous pourrions créer un service". "Ok, écrivons un test pour un tel service"

Bryan Oakley
la source
0

Une fonctionnalité peut avoir plusieurs conceptions et TDD ne vous dira pas lequel est le meilleur. Même si les tests vous aident à construire un code plus modulaire, cela peut également vous amener à construire des modules qui répondent aux exigences des tests et non à la réalité de la production. Vous devez donc comprendre où vous allez et comment les choses devraient s’intégrer dans l’ensemble. Autrement dit, il existe des exigences fonctionnelles et non fonctionnelles, n'oubliez pas la dernière.

En ce qui concerne la conception, je me réfère aux livres de Robert C. Martin (développement agile), mais aussi aux modèles d'architecture d'application et de conception de domaine de Martin Fowler. Ce dernier point est particulièrement systématique pour extraire les entités et les relations des exigences.

Ensuite, lorsque vous aurez une idée précise des options à votre disposition pour gérer ces entités, vous pourrez alimenter votre approche TDD.

Ludovic
la source
0

Le TDD n’a-t-il pas pour but de me faire découvrir mon design?

Non.

Comment pouvez-vous tester quelque chose que vous n'avez pas conçu en premier?

Pour illustrer, imaginez ces 3 exigences:

  • Un catalogue doit avoir une liste de produits
  • Le catalogue doit rappeler quels produits un utilisateur a vus
  • Les utilisateurs devraient pouvoir rechercher un produit

Ce ne sont pas des exigences, ce sont des définitions de données. Je ne sais pas en quoi consiste votre logiciel, mais il est peu probable que les analystes parlent de cette façon.

Vous devez savoir quels sont les invariants de votre système.

Une exigence serait quelque chose comme:

  • Un client peut commander un produit d'une certaine quantité, s'il y en a suffisamment en stock.

Donc, si c'est la seule exigence, vous pouvez avoir une classe comme:

public class Product {

  private int quantity;

  public Product(int initialQuantity) {
    this.quantity = initialQuantity;
  }

  public void order(int quantity) {
    // To be implemented.
  }

}

Ensuite, en utilisant TDD, vous écririez un scénario de test avant d'implémenter la méthode order ().

public void ProductTest() {

    public void testCorrectOrder() {

        Product p = new Product(10);
        p.order(3);
        p.order(4);

    }

    @Expect(ProductOutOfStockException)
    public void testIncorrectOrder() {

        Product p = new Product(10);
        p.order(7);
        p.order(4);

    }

}

Ainsi, le second test échouera. Vous pourrez alors implémenter la méthode order () comme vous le souhaitez.

Guillaume
la source
0

Vous avez tout à fait raison, le TDD se traduira par une bonne implémentation d’une conception donnée. Cela n'aidera pas votre processus de conception.

James Anderson
la source
Cependant, cela vous donne le filet de sécurité pour améliorer la conception sans enfreindre le code de travail. C'est le refactoring que la plupart des gens ignorent.
Adrian Schneider
-3

TDD aide beaucoup, cependant il y a une partie importante dans le développement de logiciel. Le développeur doit écouter le code en cours d’écriture. Le refactoring est la 3ème partie du cycle TDD. C’est la principale étape à laquelle les développeurs doivent se concentrer et réfléchir avant de passer au prochain test rouge. Y a-t-il une duplication? Les principes SOLID sont-ils appliqués? Qu'en est-il de la forte cohésion et du faible couplage? Qu'en est-il des noms? Examinez de plus près le code issu des tests et voyez s'il y a quelque chose à modifier, à revoir. Questionnez le code et le code vous dira comment il doit être conçu. J'écris habituellement des ensembles de tests multiples, examine cette liste et crée le premier modèle simple. Il n'est pas nécessaire qu'il soit final, ce n'est généralement pas le cas, car il a changé lors de l'ajout de nouveaux tests. C'est là que le design vient.

Adronius
la source