Crée les objets dont vous pensez avoir besoin dans un premier test en TDD

15

Je suis assez nouveau sur TDD et j'ai des problèmes lors de la création de mon premier test avant le code d'implémentation. Sans aucun cadre pour le code d'implémentation, je suis libre d'écrire mon premier test comme je veux, mais il semble toujours être entaché par ma façon de penser Java / OO sur le problème.

Par exemple, dans mon Github ConwaysGameOfLifeExemple le premier test que j'ai écrit (rule1_zeroNeighbours), j'ai commencé par créer un objet GameOfLife qui n'avait pas encore été implémenté; a appelé une méthode set qui n'existait pas, une méthode step qui n'existait pas, une méthode get qui n'existait pas, puis a utilisé une assertion.

Les tests ont évolué au fur et à mesure que j'écrivais plus de tests et refactorisais, mais à l'origine, cela ressemblait à ceci:

@Test
public void rule1_zeroNeighbours()
{
    GameOfLife gameOfLife = new GameOfLife();
    gameOfLife.set(1, 1, true);
    gameOfLife.step();
    assertEquals(false, gameOfLife.get(1, 1));
}

Cela semblait étrange car je forçais la conception de l'implémentation en fonction de la façon dont j'avais décidé à ce stade précoce d'écrire ce premier test.

Dans la façon dont vous comprenez le TDD, est-ce correct? Je semble suivre les principes TDD / XP en ce que mes tests et implémentations ont évolué au fil du temps avec le refactoring, et donc si cette conception initiale s'était révélée inutile, elle aurait été ouverte à changement, mais j'ai l'impression de forcer une direction sur la solution en commençant de cette façon.

Sinon, comment les gens utilisent TDD? J'aurais pu passer par plus d'itérations de refactoring en commençant avec aucun objet GameOfLife, seulement des primitives et des méthodes statiques mais cela semble trop artificiel.

Encaitar
la source
5
TDD ne remplace ni une planification minutieuse ni une sélection minutieuse des modèles de conception. Cela dit, avant d'avoir écrit une implémentation pour satisfaire les premières lignes de test, c'est un bien meilleur moment qu'après avoir écrit des dépendances pour reconnaître que votre plan est stupide, que vous avez choisi le mauvais modèle, ou même juste qu'il est gênant ou déroutant d'invoquer une classe de la manière dont votre test l'exige.
svidgen

Réponses:

9

Cela semblait étrange car je forçais la conception de l'implémentation en fonction de la façon dont j'avais décidé à ce stade précoce d'écrire ce premier test.

Je pense que c'est le point clé de votre question, la question de savoir si cela est souhaitable dépend de si vous vous penchez vers l'idée de codeninja que vous devriez concevoir à l'avance puis utiliser TDD pour remplir l'implémentation, ou l'idée de durron que les tests devraient être impliqués dans chasser la conception ainsi que la mise en œuvre.

Je pense que celui que vous préférez (ou où vous vous situez au milieu) est quelque chose que vous devez découvrir par vous-même en tant que préférence. Il est utile de comprendre les avantages et les inconvénients de chaque approche. Il y en a probablement beaucoup, mais je dirais que les principaux sont:

Pro Upfront Design

  • Aussi bon qu'un processus TDD soit à la base de la conception, il n'est pas parfait. TDing sans une destination concrète à l'esprit peut parfois aboutir à des impasses, et au moins certaines de ces impasses auraient pu être évitées avec un peu de réflexion initiale sur l'endroit où vous souhaitez vous retrouver. Cet article de blog fait cet argument en utilisant l'exemple du kata des chiffres romains, et a une implémentation finale plutôt sympa à montrer.

Conception Pro Test-Driving

  • En construisant votre implémentation autour d'un client de votre code (vos tests), vous obtenez l'adhésion à YAGNI presque gratuitement, tant que vous ne commencez pas à écrire des cas de test inutiles. Plus généralement, vous obtenez une API conçue autour de son utilisation par un consommateur, ce qui est finalement ce que vous voulez.

  • L'idée de dessiner un tas de diagrammes UML avant d'écrire n'importe quel code puis de simplement combler les lacunes est agréable, mais rarement réaliste. Dans Code Complete de Steve McConnell, le design est décrit comme un "problème méchant" - un problème que vous ne pouvez pas comprendre sans d'abord le résoudre au moins partiellement. Ajoutez à cela le fait que le problème sous-jacent lui-même peut changer à travers des exigences changeantes, et ce modèle de conception commence à se sentir un peu désespéré. Les essais de conduite vous permettent de mordre un morceau de travail à la fois - dans la conception, pas seulement la mise en œuvre - et de savoir qu'au moins pendant la durée de vie du rouge au vert, cette tâche sera toujours à jour et pertinente.

En ce qui concerne votre exemple particulier, comme le dit Durron, si vous optiez pour une approche consistant à éliminer la conception en écrivant le test le plus simple, en consommant l'interface minimale possible, vous commenceriez probablement avec une interface plus simple que celle de votre extrait de code .

Ben Aaronson
la source
Le lien était une très bonne lecture Ben. Merci de partager ça.
RubberDuck
1
@RubberDuck Vous êtes les bienvenus! Je ne suis pas entièrement d'accord avec cela, en fait, mais je pense que c'est un excellent travail pour défendre ce point de vue.
Ben Aaronson
1
Je ne suis pas sûr de le faire non plus, mais cela se justifie bien. Je pense que la bonne réponse se situe quelque part au milieu. Vous devez avoir un plan, mais certainement si vos tests vous semblent maladroits, repensez. Quoi qu'il en soit ... ++ bon spectacle vieux haricot.
RubberDuck
17

Afin d'écrire le test en premier lieu, vous devez concevoir l'API que vous allez ensuite implémenter. Vous avez déjà commencé du mauvais pied en écrivant votre test pour créer l' objet entier GameOfLife et en l'utilisant pour implémenter votre test.

De tests unitaires pratiques avec JUnit et Mockito :

Au début, vous pourriez vous sentir gêné d'écrire quelque chose qui n'est même pas là. Cela nécessite un léger changement dans vos habitudes de codage, mais après un certain temps, vous verrez que c'est une excellente opportunité de conception. En écrivant d'abord des tests, vous avez la possibilité de créer une API qui est pratique pour un client. Votre test est le premier client d'une API nouvellement née. C'est vraiment ce qu'est TDD: la conception d'une API.

Votre test ne fait pas beaucoup de tentative de conception d'une API. Vous avez configuré un système avec état où toutes les fonctionnalités sont contenues dans la GameOfLifeclasse externe .

Si je devais écrire cette application, je penserais plutôt aux pièces que je veux construire. Par exemple, je pourrais créer une Cellclasse, écrire des tests pour cela, avant de passer à l'application plus grande. Je voudrais certainement créer une classe pour la structure de données "infinie dans toutes les directions" qui est nécessaire pour implémenter correctement Conway, et tester cela. Une fois que tout cela a été fait, je penserais à écrire la classe globale qui a une mainméthode et ainsi de suite.

Il est facile de passer outre l'étape «écrire un test qui échoue». Mais l'écriture du test d'échec qui fonctionne comme vous le souhaitez est au cœur de TDD.

durron597
la source
1
Considérer une cellule ne serait qu'une enveloppe pour une boolean, cette conception serait certainement pire pour les performances. À moins qu'il doive être extensible à l'avenir à d'autres automates cellulaires avec plus de deux états?
user253751
2
@immibis C'est l'ergotage sur les détails. Vous pouvez commencer avec une classe qui représente la collection de cellules. Vous pouvez également migrer / fusionner la classe de cellules et ses tests avec une classe représentant une collection de cellules ultérieurement si les performances sont un problème.
Eric
@immibis Le nombre de voisins vivants peut être stocké pour des raisons de performances. Nombre de tiques que la cellule a été vivante, pour des raisons de coloration.
Blorgbeard est sorti
L'optimisation prématurée @immibis est le mal ... De plus, éviter l'obsession primitive est le meilleur moyen d'écrire du code de bonne qualité, quel que soit le nombre d'états pris en charge. Jetez un œil à: jamesshore.com/Blog/PrimitiveObsession.html
Paul
0

Il y a une école de pensée différente à ce sujet.

Certains disent: le test ne compile pas est une erreur - allez corriger le plus petit code de production disponible.

Certains disent: il est bon d'écrire le test d'abord vérifier s'il aspire (ou non) la fourmi puis créer les classes / méthodes manquantes

Avec la première approche, vous êtes vraiment dans un cycle rouge-vert-refactor. Avec le second, vous avez un aperçu un peu plus large de ce que vous souhaitez réaliser.

C'est à vous de choisir la façon dont vous travaillez. À mon humble avis, les deux approches sont valides.

timoras
la source
0

Même lorsque j'implémente quelque chose d'une manière «hack it together», je pense toujours aux classes et aux étapes qui seront impliquées dans l'ensemble du programme. Vous avez donc réfléchi et écrit ces idées de conception comme test en premier - c'est génial!

Continuez maintenant à parcourir les deux implémentations pour réaliser ce test initial, puis ajoutez d'autres tests pour améliorer et étendre la conception.

Ce qui pourrait vous aider, c'est d'utiliser du concombre ou similaire pour écrire vos tests.

gbjbaanb
la source
0

Avant de commencer à écrire vos tests, vous devez réfléchir à la façon de concevoir votre système. Vous devez consacrer un temps considérable à votre phase de conception. Si vous l'avez fait, vous n'aurez pas cette confusion sur TDD.

TDD n'est qu'un lien d' approche de développement : TDD
1. Ajoutez un test
2. Exécutez tous les tests et voyez si le nouveau échoue
3. Écrivez du code
4. Exécutez les tests
5. Code de refactorisation
6. Répétez

TDD vous aide à couvrir toutes les fonctionnalités requises ce que vous avez prévu avant de commencer à développer votre logiciel. lien: Avantages

codeninja.sj
la source
0

Je n'aime pas les tests de niveau système écrits en java ou C # pour cette raison. Regardez SpecFlow pour c # ou l'un des framework de test basé sur Cucumber pour java (peut-être JBehave). Vos tests peuvent alors ressembler davantage à ceci.

entrez la description de l'image ici

Et vous pouvez changer la conception de votre objet sans avoir à modifier tous vos tests système.

(Les tests unitaires «normaux» sont parfaits pour tester des classes individuelles.)

Quelles sont les différences entre les frameworks BDD pour Java?

Ian
la source