Quand écrivez-vous le «vrai» code dans TDD?

147

Tous les exemples que j'ai lus et vus sur les vidéos de formation ont des exemples simplistes. Mais ce que je ne vois pas si je fais le "vrai" code une fois que je suis vert. Est-ce la partie "Refactor"?

Si j’ai un objet assez complexe avec une méthode complexe, j’écris mon test et le strict minimum pour le faire passer (après sa première défaillance, le rouge). Quand dois-je revenir en arrière et écrire le code réel? Et combien de code dois-je écrire avant de refaire le test? Je devine que le dernier est plus d'intuition.

Edit: Merci à tous ceux qui ont répondu. Toutes vos réponses m'ont énormément aidé. Il semble y avoir différentes idées sur ce que je demandais ou sur quoi je m'embarrassais, peut-être qu'il y en avait un, mais ce que je demandais, c'était: disons que j'ai une application pour la construction d'une école.

Dans ma conception, je veux commencer par une architecture, User Stories, etc. À partir de là, je prends ces user stories et je crée un test pour tester la user story. L'utilisateur dit, nous avons des gens qui s'inscrivent à l'école et paient des frais d'inscription. Alors, je pense à un moyen de faire échouer cela. Ce faisant, je conçois un test de classe pour la classe X (peut-être étudiant), qui échouera. Je crée ensuite la classe "Student". Peut-être que "l'école" je ne sais pas.

Quoi qu’il en soit, la conception de TD me force à réfléchir à l’histoire. Si je peux faire échouer un test, je sais pourquoi il échoue, mais cela suppose que je puisse le faire passer. Il s'agit de la conception.

Je compare cela à la réflexion sur la récursivité. La récursivité n'est pas un concept difficile. Il est peut-être plus difficile d’en garder une trace dans votre tête, mais en réalité, le plus difficile est de savoir, quand la récursivité "se déclenche", quand arrêter (à mon avis, bien sûr). Je dois donc penser à ce qui cesse la récursivité en premier. Ce n'est qu'une analogie imparfaite, et elle suppose que chaque itération récursive est un "pass". Encore une fois, juste un avis.

Dans la mise en œuvre, l'école est plus difficile à voir. Les grands livres numériques et bancaires sont "faciles" dans le sens où vous pouvez utiliser une arithmétique simple. Je peux voir a + b et retourner 0, etc. Dans le cas d'un système de personnes, je dois réfléchir plus sérieusement à la façon de mettre en œuvre cela. J'ai le concept de l'échec, passe, refactor (principalement en raison de l'étude et de cette question.)

Ce que je ne sais pas est basé sur le manque d'expérience, à mon avis. Je ne sais pas comment échouer en inscrivant un nouvel étudiant. Je ne sais pas comment faire échouer une personne en tapant un nom de famille et que celui-ci soit enregistré dans une base de données. Je sais comment faire un + 1 pour les mathématiques simples, mais avec des entités comme une personne, je ne sais pas si je ne fais que tester pour voir si je récupère un ID unique de base de données ou quelque chose d'autre lorsque quelqu'un entre un nom dans un base de données ou les deux ou ni l'un ni l'autre.

Ou peut-être que cela montre que je suis toujours confus.

johnny
la source
193
Après le TDD, les gens rentrent chez eux pour la nuit.
Hobbs
14
Pourquoi pensez-vous que le code que vous avez écrit n'est pas réel?
Goyo
2
@RubberDuck Plus que les autres réponses. Je suis sûr que j'y reviendrai bientôt. C'est toujours un peu étranger, mais je ne vais pas l'abandonner. Ce que vous avez dit était logique. J'essaie simplement de donner un sens à mon contexte ou à une application métier classique. Peut-être un système d'inventaire ou similaire. Je dois y penser. Je suis reconnaissant pour votre temps cependant. Merci.
johnny
1
Les réponses sont déjà évidentes, mais tant que tous vos tests sont réussis et que vous n'avez pas besoin de nouveaux tests / fonctionnalités, on peut supposer que le code que vous avez est terminé, barré.
ESR
3
Dans la question, il y a une hypothèse qui peut poser problème dans "J'ai un objet assez complexe avec une méthode complexe". Dans TDD, vous écrivez d’abord vos tests, vous commencez donc avec un code assez simple. Cela vous obligera à coder une structure conviviale pour les tests, qui devra être modulaire. Un comportement si complexe sera créé en combinant des objets plus simples. Si vous vous retrouvez avec un objet ou une méthode assez complexe, vous effectuez une refactorisation
Borjab

Réponses:

242

Si j’ai un objet assez complexe avec une méthode complexe, j’écris mon test et le strict minimum pour le faire passer (après sa première défaillance, le rouge). Quand dois-je revenir en arrière et écrire le code réel? Et combien de code dois-je écrire avant de refaire le test? Je devine que le dernier est plus d'intuition.

Vous ne "retournez" pas et écrivez du "code réel". C'est tout le code réel. Ce que vous faites est de revenir en arrière et d’ajouter un autre test qui vous oblige à modifier votre code afin de faire passer le nouveau test.

Quant à combien de code écrivez-vous avant de refaire le test? Aucun. Vous écrivez zéro code sans échec du test qui vous oblige à écrire plus de code.

Remarquez le motif?

Passons en revue (un autre) exemple simple dans l'espoir que cela aide.

Assert.Equal("1", FizzBuzz(1));

Facile peazy.

public String FizzBuzz(int n) {
    return 1.ToString();
}

Pas ce que vous appelleriez du vrai code, non? Ajoutons un test qui force un changement.

Assert.Equal("2", FizzBuzz(2));

Nous pourrions faire quelque chose d'idiot if n == 1, mais nous allons passer à la solution sensée.

public String FizzBuzz(int n) {
    return n.ToString();
}

Cool. Cela fonctionnera pour tous les numéros non FizzBuzz. Quelle est la prochaine entrée qui forcera le code de production à changer?

Assert.Equal("Fizz", FizzBuzz(3));

public String FizzBuzz(int n) {
    if (n == 3)
        return "Fizz";
    return n.ToString();
}

Et encore. Ecrivez un test qui ne passe pas encore.

Assert.Equal("Fizz", FizzBuzz(6));

public String FizzBuzz(int n) {
    if (n % 3 == 0)
        return "Fizz";
    return n.ToString();
}

Et nous avons maintenant couvert tous les multiples de trois (qui ne sont pas également des multiples de cinq, nous le noterons et reviendrons).

Nous n'avons pas encore écrit de test pour "Buzz", alors écrivons ça.

Assert.Equal("Buzz", FizzBuzz(5));

public String FizzBuzz(int n) {
    if (n % 3 == 0)
        return "Fizz";
    if (n == 5)
        return "Buzz"
    return n.ToString();
}

Et encore une fois, nous savons qu'il y a un autre cas que nous devons traiter.

Assert.Equal("Buzz", FizzBuzz(10));

public String FizzBuzz(int n) {
    if (n % 3 == 0)
        return "Fizz";
    if (n % 5 == 0)
        return "Buzz"
    return n.ToString();
}

Et maintenant, nous pouvons gérer tous les multiples de 5 qui ne sont pas également des multiples de 3.

Jusqu'ici, nous avions ignoré l'étape de la refactorisation, mais je vois des doublons. Nettoyons cela maintenant.

private bool isDivisibleBy(int divisor, int input) {
    return (input % divisor == 0);
}

public String FizzBuzz(int n) {
    if (isDivisibleBy(3, n))
        return "Fizz";
    if (isDivisibleBy(5, n))
        return "Buzz"
    return n.ToString();
}

Cool. Maintenant, nous avons supprimé la duplication et créé une fonction bien nommée. Quel est le prochain test que nous pouvons écrire qui nous obligera à changer le code? Eh bien, nous évitons le cas où le nombre est divisible par 3 et 5. Écrivons-le maintenant.

Assert.Equal("FizzBuzz", FizzBuzz(15));

public String FizzBuzz(int n) {
    if (isDivisibleBy(3, n) && isDivisibleBy(5, n))
        return "FizzBuzz";
    if (isDivisibleBy(3, n))
        return "Fizz";
    if (isDivisibleBy(5, n))
        return "Buzz"
    return n.ToString();
}

Les tests réussissent, mais nous avons plus de duplication. Nous avons des options, mais je vais appliquer "Extract Local Variable" plusieurs fois afin que nous procédions au refactoring au lieu de la réécriture.

public String FizzBuzz(int n) {

    var isDivisibleBy3 = isDivisibleBy(3, n);
    var isDivisibleBy5 = isDivisibleBy(5, n);

    if ( isDivisibleBy3 && isDivisibleBy5 )
        return "FizzBuzz";
    if ( isDivisibleBy3 )
        return "Fizz";
    if ( isDivisibleBy5 )
        return "Buzz"
    return n.ToString();
}

Et nous avons couvert chaque entrée raisonnable, mais qu'en est-il des entrées déraisonnables ? Que se passe-t-il si nous passons 0 ou un négatif? Écrivez ces cas de test.

public String FizzBuzz(int n) {

    if (n < 1)
        throw new InvalidArgException("n must be >= 1);

    var isDivisibleBy3 = isDivisibleBy(3, n);
    var isDivisibleBy5 = isDivisibleBy(5, n);

    if ( isDivisibleBy3 && isDivisibleBy5 )
        return "FizzBuzz";
    if ( isDivisibleBy3 )
        return "Fizz";
    if ( isDivisibleBy5 )
        return "Buzz"
    return n.ToString();
}

Cela commence-t-il à ressembler à du "code réel"? Plus important encore, à quel moment a-t-il cessé d'être un "code irréel" et de devenir "réel"? C'est quelque chose à méditer sur ...

J'ai donc pu le faire simplement en recherchant un test qui, je le savais, ne réussirait pas à chaque étape, mais je me suis beaucoup entraîné. Quand je suis au travail, les choses ne sont pas toujours aussi simples et je ne sais pas toujours quel test va forcer un changement. Parfois, j'écris un test et je suis surpris de voir qu'il passe déjà! Je vous recommande vivement de prendre l’habitude de créer une "liste de tests" avant de commencer. Cette liste de test doit contenir toutes les entrées "intéressantes" auxquelles vous pouvez penser. Vous ne les utiliserez peut-être pas tous et vous ajouterez probablement des cas au fur et à mesure, mais cette liste sert de feuille de route. Ma liste de tests pour FizzBuzz ressemblerait à ceci.

  • Négatif
  • Zéro
  • Un
  • Deux
  • Trois
  • Quatre
  • Cinq
  • Six (multiple non trivial de 3)
  • Neuf (3 carrés)
  • Dix (multiple non trivial de 5)
  • 15 (multiple de 3 et 5)
  • 30 (multiple de 3 & 5 non trivial)
Canard en caoutchouc
la source
3
Les commentaires ne sont pas pour une discussion prolongée; cette conversation a été déplacée pour discuter .
maple_shaft
47
À moins que je ne comprenne totalement cette réponse: "Nous pourrions faire quelque chose de stupide comme si n == 1, mais nous allons passer à la solution sensée." - le tout était stupide. Si vous savez dès le départ que vous voulez une fonction qui spécifie <spec>, écrivez des tests pour <spec> et ignorez la partie sur laquelle vous écrivez des versions qui échouent de toute évidence <spec>. Si vous trouvez un bogue dans <spec>, assurez-vous: écrivez d'abord un test pour vérifier que vous pouvez l'exercer avant le correctif et observer que le test réussit après le correctif. Mais il n'est pas nécessaire de simuler toutes ces étapes intermédiaires.
GManNickG
16
Les commentaires qui soulignent les principales lacunes de cette réponse et de TDD en général ont été déplacés vers le chat. Si vous envisagez d'utiliser TDD, veuillez lire le "chat". Malheureusement, les commentaires de «qualité» sont maintenant cachés parmi une foule de discussions que les futurs étudiants pourront lire.
user3791372
2
@GManNickG Je crois que le but est d'obtenir la bonne quantité de tests. Écrire les tests à l’avance permet d’oublier facilement les cas particuliers à tester, ce qui peut conduire soit à des situations qui ne sont pas suffisamment couvertes dans les tests, soit à des situations essentiellement identiques. Si vous pouvez le faire sans ces étapes intermédiaires, c'est parfait! Cependant, tout le monde ne peut pas encore le faire, mais cela demande de la pratique.
hvd
1
Et voici une citation de Kent Beck sur le refactoring: "Maintenant que le test est exécuté, nous pouvons réaliser (comme dans" rendre réel ") l'implémentation de summary ()". Il passe ensuite à changer une constante en une variable. J'ai senti que cette citation correspondait assez bien à la question.
Chris Wohlert
46

Le "vrai" code est le code que vous écrivez pour réussir votre test. Vraiment . C'est si simple.

Lorsque les gens parlent d'écrire le strict minimum pour rendre le test vert, cela signifie simplement que votre vrai code doit suivre le principe YAGNI .

L’idée de l’étape de la refactorisation est simplement de nettoyer ce que vous avez écrit une fois que vous êtes satisfait qu’il répond aux exigences.

Tant que les tests que vous écrivez englobent réellement les exigences de votre produit, une fois qu'ils sont satisfaisants, le code est complet. Pensez-y, si toutes vos exigences professionnelles ont un test et que tous ces tests sont verts, que reste-t-il à écrire? (D'accord, dans la vie réelle, nous n'avons pas tendance à avoir une couverture complète des tests, mais la théorie est valable.)

GénériqueJon
la source
45
Les tests unitaires ne peuvent pas réellement couvrir les exigences de votre produit, même pour des exigences relativement triviales. Au mieux, ils échantillonnent l'espace d'entrée-sortie et l'idée est que vous généralisez (correctement) à tout l'espace d'entrée-sortie. Bien sûr, votre code pourrait simplement être un gros switchcas avec un cas pour chaque test unitaire qui réussirait tous les tests et échouerait pour toute autre entrée.
Derek Elkins
8
@DerekElkins TDD impose l'échec des tests. Ne pas échouer les tests unitaires.
Taemyr
6
@DerekElkins c'est pourquoi vous n'écrivez pas simplement des tests unitaires, mais aussi pourquoi il y a une hypothèse générale que vous essayez de faire quelque chose de pas simulé!
jonrsharpe
36
@jonrsharpe Par cette logique, je n'écrirais jamais d'implémentations triviales. Par exemple, dans l'exemple de FizzBuzz dans la réponse de RubberDuck (qui utilise uniquement des tests unitaires), la première mise en œuvre est clairement "simulée". Ma compréhension de la question est exactement cette dichotomie entre écrire du code que vous savez être incomplet et un code que vous croyez vraiment mettre en œuvre l’exigence, le "vrai code". Mon "grand switch" était conçu comme un extrême logique consistant à "écrire le strict minimum pour rendre les tests écologiques". Pour moi, la question du PO est la suivante: où est le principe qui évite ce gros problème dans TDD switch?
Derek Elkins
2
@GenericJon C'est un peu trop optimiste dans mon expérience :) D'une part, il y a des gens qui aiment le travail répétitif sans cervelle. Ils seront plus heureux avec une déclaration de commutateur géant qu'avec une "prise de décision compliquée". Et pour perdre leur emploi, ils auraient soit besoin de quelqu'un qui les appelait à la technique (et ils auraient mieux fait de prouver que c'est en train de perdre les opportunités / l'argent de la société!), Soit exceptionnellement mal. Après avoir pris en charge la maintenance de nombreux projets de ce type, je peux dire qu'il est facile pour un code très naïf de durer des décennies, à condition que cela rende le client heureux (et payant).
Luaan
14

La réponse courte est que le "code réel" est le code qui fait passer le test. Si vous pouvez réussir votre test avec autre chose que du code réel, ajoutez d'autres tests!

Je conviens que de nombreux tutoriels sur le TDD sont simplistes. Cela fonctionne contre eux. Un test trop simple pour une méthode qui, par exemple, calcule 3 + 8 n'a vraiment pas d'autre choix que de calculer également 3 + 8 et de comparer le résultat. Cela donne l’impression que vous allez simplement dupliquer le code, et que les tests représentent un travail inutile inutile, source d’erreurs.

Lorsque vous êtes bon en test, cela vous aidera à structurer votre application et à écrire votre code. Si vous ne parvenez pas à faire des tests utiles et judicieux, vous devriez probablement repenser un peu votre conception. Un système bien conçu est facile à tester - ce qui signifie que les tests sensibles sont faciles à concevoir et à mettre en œuvre.

Lorsque vous écrivez vos tests d’abord, observez leur échec, puis écrivez le code qui les fait passer. C’est une discipline qui garantit que tout votre code dispose des tests correspondants. Je ne suis pas esclave de cette règle quand je code; souvent j'écris des tests après coup. Mais faire les premiers tests aide à rester honnête. Avec une certaine expérience, vous remarquerez que vous vous codez dans un coin, même lorsque vous n’écrivez pas d’abord des tests.

Carl Raymond
la source
6
Personnellement, le test que j'écrirais serait assertEqual(plus(3,8), 11), non assertEqual(plus(3,8), my_test_implementation_of_addition(3,8)). Pour les cas plus complexes, vous recherchez toujours un moyen de prouver que le résultat est correct, autre que le calcul dynamique du résultat correct dans le test et la vérification de l'égalité.
Steve Jessop
Donc , pour vraiment stupide façon de le faire pour cet exemple, vous pouvez prouver que plus(3,8)a retourné le résultat correct en soustrayant 3 de celle - ci, en soustrayant 8 de cela, et de vérifier le résultat contre 0. Il en est ainsi de toute évidence équivalente à assertEqual(plus(3,8), 3+8)à un C'est un peu absurde, mais si le code testé crée quelque chose de plus compliqué qu'un simple nombre entier, prendre le résultat et vérifier l'exactitude de chaque partie est souvent la bonne approche. Alternativement, quelque chose commefor (i=0, j=10; i < 10; ++i, ++j) assertEqual(plus(i, 10), j)
Steve Jessop
... puisque cela évite la grande crainte, à savoir que lors de l'écriture du test, nous commettions la même erreur au sujet de "comment ajouter 10" que nous avons faite dans le code live. Donc, le test évite soigneusement d’écrire un code qui ajoute 10 à tout, dans le test qui plus()peut ajouter 10 à des choses. Nous nous basons toujours sur les valeurs de boucle initiales vérifiées par le programmeur, bien sûr.
Steve Jessop
4
Je veux juste souligner que même si vous écrivez des tests après coup, c'est toujours une bonne idée de les regarder échouer; trouvez une partie du code qui semble cruciale pour tout ce sur quoi vous travaillez, peaufinez-la un peu (par exemple remplacez un + par un - ou autre), lancez les tests et regardez-les échouer, annulez la modification et regardez-les passer. Plusieurs fois, j'ai fait cela, le test n'échoue pas, ce qui le rend pire qu'inutile: non seulement il ne teste rien, il me donne une fausse assurance que quelque chose est en train d'être testé!
Warbo
6

Parfois, certains exemples sur le TDD peuvent être trompeurs. Comme d'autres personnes l'ont déjà fait remarquer, le code que vous écrivez pour réussir les tests est le code réel.

Mais ne pensez pas que le vrai code apparaît comme par magie - c'est faux. Vous avez besoin d'une meilleure compréhension de ce que vous voulez atteindre, puis de sélectionner le test en conséquence, en partant des cas les plus faciles et des cas critiques.

Par exemple, si vous devez écrire un lexer, vous commencez par une chaîne vide, puis par une série d'espaces, puis par un nombre, puis par un nombre entouré d'espaces, puis par un nombre incorrect, etc. Ces petites transformations vous mèneront à Le bon algorithme, mais vous ne sautez pas du cas le plus facile à un cas très complexe choisi bêtement pour obtenir le code réel.

Bob Martin l'explique parfaitement ici .

Victor Cejudo
la source
5

La partie refactor est nettoyée lorsque vous êtes fatigué et que vous voulez rentrer chez vous.

Lorsque vous êtes sur le point d'ajouter une fonctionnalité, la partie refactor est ce que vous modifiez avant le prochain test. Vous refactorisez le code pour faire de la place pour la nouvelle fonctionnalité. Vous faites cela lorsque vous savez quelle sera cette nouvelle fonctionnalité. Pas quand vous l'imaginez.

Cela peut être aussi simple que de renommer GreetImplà GreetWorldavant de créer une GreetMomclasse (après l' ajout d' un test) pour ajouter une fonctionnalité qui imprimera « Salut maman ».

confits_orange
la source
1

Mais le vrai code apparaîtrait dans la phase de refactorisation de la phase TDD. C'est-à-dire le code qui devrait faire partie de la version finale.

Les tests doivent être exécutés chaque fois que vous effectuez un changement.

La devise du cycle de vie du TDD serait: REFACTOR RED GREEN

RED : Écris les tests

VERT : Faites une tentative honnête pour obtenir du code fonctionnel qui passe les tests le plus rapidement possible: dupliquer le code, nommer de manière obscure des variables de hacks du plus haut ordre, etc.

REFACTOR : Nettoyez le code, nommez correctement les variables. Séchez le code.

graeme
la source
6
Je sais ce que vous dites à propos de la phase "verte", mais cela implique que les valeurs renvoyées par un câblage définitif pour réussir les tests pourraient être appropriées. D'après mon expérience, "vert" devrait être une tentative honnête de rendre le code fonctionnel conforme à l'exigence. Il n'est peut-être pas parfait, mais il doit être aussi complet et "expédiable" que le développeur peut gérer dès le premier passage. Il est probablement préférable de refactoriser un peu plus tard, une fois que vous avez développé davantage, les problèmes avec le premier passage deviennent plus évidents et les opportunités de DRY apparaissent.
Mcottle
2
@mcottle: vous serez peut-être surpris du nombre d'implémentations d'un référentiel get-only pouvant être des valeurs codées en dur dans la base de code. :)
Bryan Boettcher
6
Pourquoi devrais-je écrire un code de merde et le nettoyer, alors que je peux créer un code de qualité de production agréable aussi rapidement que je peux taper? :)
Kaz
1
@Kaz Parce que de cette façon, vous risquez d'ajouter un comportement non testé . Le seul moyen de vérifier si chaque comportement souhaité est mis à l’essai est de procéder aux changements les plus simples possibles, quel que soit son état. Parfois, la refactorisation suivante fait apparaître une nouvelle approche à laquelle vous n'aviez pas pensé à l'avance ...
Timothy Truckle
1
@ TimothyTruckle Et si cela prend 50 minutes pour trouver le changement le plus simple possible, mais seulement 5 minutes pour trouver le deuxième changement le plus simple possible? Allons-nous avec deuxième plus simple ou continuons-nous à chercher plus simple?
Kaz
1

Quand écrivez-vous le «vrai» code dans TDD?

La phase rouge correspond à l’ écriture du code.

Dans la phase de refactoring , l’objectif principal est de supprimer du code.

Dans la phase rouge , vous faites tout pour que le test réussisse le plus rapidement possible et à tout prix . Vous ignorez totalement ce que vous avez déjà entendu parler de bonnes pratiques de codage ou de modèle de conception. Rendre le test vert est tout ce qui compte.

Lors de la phase de refactoring , vous nettoyez le désordre que vous venez de créer. À présent, vous devez d’abord vérifier si le changement que vous venez d’apporter figure en haut de la liste dans la liste Priorité de transformation et s’il existe une duplication de code, vous pouvez le supprimer en appliquant probablement un modèle de conception.

Enfin, vous améliorez la lisibilité en renommant les identifiants et en extrayant des nombres magiques et / ou des chaînes littérales en constantes.


Ce n'est pas un refactor rouge, c'est un refactor rouge-vert. - Rob Kinyon

Merci d'avoir signalé cela.

C'est donc la phase verte où vous écrivez le vrai code

Dans la phase rouge , vous écrivez la spécification de l' exécutable ...

Timothy Truckle
la source
Ce n'est pas un refactor rouge, c'est un refactor rouge-vert. Le "rouge" signifie que vous passez de vert à votre suite de tests (tous les tests réussissent) à rouge (un test échoue). Le "vert" est l'endroit où vous passez négligemment votre suite de tests du rouge (un test échoue) au vert (tous les tests réussissent). Le "refactor" est l'endroit où vous prenez votre code et le rendre joli tout en gardant tous les tests réussis.
Rob Kinyon
1

Vous écrivez Real Code tout le temps.

A chaque étape, vous écrivez du code pour satisfaire les conditions que votre code satisfera pour les futurs appelants de votre code (que vous soyez ou non ...).

Vous pensez que vous n'écrivez pas de code utile ( réel ), car dans un moment vous pourriez le reformuler.

Le refactoring de code est le processus de restructuration du code informatique existant - modification de la factorisation - sans changer son comportement externe.

Cela signifie que même si vous modifiez le code, les conditions de satisfaction du code restent inchangées. Et les contrôles ( tests ) que vous avez mis en place pour vérifier votre code sont déjà là pour vérifier si vos modifications ont changé quoi que ce soit. Donc, le code que vous avez écrit tout le temps est là, mais d'une manière différente.

Une autre raison pour laquelle vous pensez peut-être que ce n'est pas du vrai code, c'est que vous donnez des exemples dans lesquels le programme final peut déjà être prévu par vous. Ceci est très bon, car il montre Vous avez des connaissances sur le domaine Vous programmez en.
Mais plusieurs fois les programmeurs sont dans un domaine qui est nouveau , inconnu pour eux. Ils ne savent pas quel sera le résultat final et TDD est une technique pour écrire des programmes étape par étape, documentant nos connaissances sur le fonctionnement de ce système et vérifiant que notre code fonctionne de cette manière.

Lorsque j'ai lu The Book (*) sur TDD, pour moi, la caractéristique la plus importante qui ressortait était la liste: TODO. Cela m'a montré que le TDD est également une technique permettant aux développeurs de se concentrer sur une chose à la fois. Donc, ceci est également une réponse à votre question à propos de Combien de code réel à écrire ? Je dirais assez de code pour se concentrer sur une chose à la fois.

(*) "Développement piloté par les tests: par exemple" de Kent Beck

Robert Andrzejuk
la source
2
"Le développement piloté par les tests: par exemple" de Kent Beck
Robert Andrzejuk
1

Vous n'écrivez pas de code pour faire échouer vos tests.

Vous écrivez vos tests pour définir à quoi devrait ressembler le succès, ce qui devrait tous initialement échouer car vous n'avez pas encore écrit le code qui va réussir.

L’écriture de tests qui échouent initialement a pour objectif essentiel de faire deux choses:

  1. Couvrir tous les cas - tous les cas nominaux, tous les cas marginaux, etc.
  2. Validez vos tests. Si vous ne les voyez jamais passer, comment pouvez-vous être sûr qu'ils rapporteront de manière fiable un échec le cas échéant?

Le point derrière le refactor rouge-vert est que l'écriture des tests corrects vous donne la confiance de savoir que le code que vous avez écrit pour réussir les tests est correcte et vous permet de refactoriser avec la certitude que vos tests vous informeront dès que quelque chose se brise, vous pouvez donc immédiatement le réparer.

Selon ma propre expérience (C # / .NET), pur-test-first est un idéal inatteignable, car vous ne pouvez pas compiler un appel à une méthode qui n'existe pas encore. Donc, "test first" consiste vraiment à coder les interfaces et les implémentations de stub en premier, puis à écrire des tests sur les stubs (qui échoueront initialement) jusqu'à ce que les stubs soient correctement étoffés. Je ne suis jamais en train d'écrire "code défaillant", juste en me basant sur des bouts.

Zenilogix
la source
0

Je pense que vous pouvez être confus entre les tests unitaires et les tests d'intégration. Je crois qu'il peut également y avoir des tests d'acceptation, mais cela dépend de votre processus.

Une fois que vous avez testé toutes les petites "unités", vous les testez toutes assemblées ou "intégrées". C'est généralement un programme complet ou une bibliothèque.

Dans le code que j'ai écrit, l'intégration teste une bibliothèque avec divers programmes de test lisant des données et les transmettant à la bibliothèque, puis vérifiant les résultats. Ensuite, je le fais avec des fils. Ensuite, je le fais avec des fils et un fork () au milieu. Ensuite, je l'exécute et tue -9 après 2 secondes, puis je le lance et vérifie son mode de récupération. Je le fuzz. Je le torture de toutes sortes de façons.

Tout cela est AUSSI un test, mais je n'ai pas un joli affichage rouge / vert pour les résultats. Soit ça réussit, soit je fouille quelques milliers de lignes de code d'erreur pour comprendre pourquoi.

C'est là que vous testez le "code réel".

Et je viens de penser à cela, mais peut-être que vous ne savez pas quand vous êtes censé avoir fini d'écrire des tests unitaires. Vous avez terminé d'écrire des tests unitaires lorsque vos tests exercent tout ce que vous avez spécifié. Parfois, vous pouvez perdre la trace de cela parmi tous les cas de traitement des erreurs et de traitement des erreurs, de sorte que vous voudrez peut-être créer un groupe de tests agréable de tests de chemin d'accès satisfaisant qui suivent simplement les spécifications.

Zan Lynx
la source
(son = possessif, c'est = "il est" ou "il a". Voir par exemple Comment utiliser son et c'est .)
Peter Mortensen
-6

En réponse au titre de la question: "Quand écrivez-vous le" vrai "code en TDD?", La réponse est: "presque jamais" ou "très lentement".

Vous parlez comme un étudiant, je vais donc vous répondre comme si vous conseilliez un étudiant.

Vous allez apprendre beaucoup de «théories» et de «techniques» de codage. Ils sont parfaits pour passer le temps sur des cours trop coûteux pour les étudiants, mais ne vous apportent que très peu d’avantages, car vous ne pourriez pas lire un livre deux fois plus vite.

Le travail d'un codeur est uniquement de produire du code. Code qui fonctionne vraiment bien. C'est pourquoi vous, le codeur, planifiez le code dans votre esprit, sur papier, dans une application appropriée, etc., et vous prévoyez de résoudre à l'avance les éventuels défauts / failles en réfléchissant logiquement et latéralement avant de coder.

Mais vous devez savoir comment casser votre application pour pouvoir concevoir un code correct. Par exemple, si vous ne connaissiez pas Little Bobby Table (xkcd 327), vous ne purifieriez probablement pas vos entrées avant de travailler avec la base de données, vous ne pourriez donc pas sécuriser vos données autour de ce concept.

TDD est simplement un flux de travail conçu pour minimiser les bugs dans votre code en créant des tests sur ce qui pourrait mal tourner avant de coder votre application, car le codage peut devenir exponentiellement difficile à mesure que vous introduisez du code et que vous oubliez les bugs auxquels vous pensiez. Une fois que vous pensez que vous avez terminé votre application, vous exécutez les tests et effectuez un boom. Espérons que vos tests détectent des bugs.

TDD n’est pas - comme le pensent certaines personnes - écrire un test, le faire passer avec un minimum de code, écrire un autre test, le réussir avec un minimum de code, etc. Il s’agit plutôt d’un moyen de vous aider à coder en toute confiance. Cet idéal de code de refactorisation continu permettant de faire fonctionner les tests est idiot, mais c’est un bon concept pour les étudiants car cela leur donne une sensation de bien-être lorsqu’ils ajoutent une nouvelle fonctionnalité et qu’ils apprennent toujours à coder ...

Veuillez ne pas tomber dans ce piège et voir votre rôle de codage pour ce qu'il est - le travail d'un codeur est uniquement de produire du code. Code qui fonctionne vraiment bien. Maintenant, souvenez-vous que vous serez à l'heure en tant que codeur professionnel et que votre client ne se souciera pas de savoir si vous avez écrit 100 000 assertions, ou 0. Ils veulent juste un code qui fonctionne. Vraiment bien, en fait.

utilisateur3791372
la source
3
Je ne suis même pas proche d'un étudiant, mais je lis et j'essaie d'appliquer de bonnes techniques et d'être professionnel. Donc, dans ce sens, je suis un "étudiant". Je pose juste des questions très basiques, car je suis comme ça. J'aime savoir exactement pourquoi je fais ce que je fais. Le cœur du problème. Si je ne comprends pas cela, je n'aime pas ça et je commence à poser des questions. J'ai besoin de savoir pourquoi, si je vais l'utiliser. TDD semble intuitivement bon à certains égards, comme savoir ce qu’il faut créer et penser, mais la mise en œuvre était difficile à comprendre. Je pense avoir une meilleure compréhension maintenant.
johnny
4
Ce sont les règles du TDD. Vous êtes libre d'écrire du code comme bon vous semble, mais si vous ne suivez pas ces trois règles, vous ne faites pas de TDD.
Sean Burton
2
"Règles" d'une personne? TDD est une suggestion pour vous aider à coder, pas une religion. C'est triste de voir autant de gens adhérer à une idée de manière aussi anale. Même l'origine du TDD est controversée.
user3791372
2
@ user3791372 TDD est un processus très strict et clairement défini. Même si beaucoup pensent que cela signifie simplement "Faites des tests lorsque vous programmez", ce n'est pas le cas. Essayons de ne pas confondre les termes ici, cette question concerne le processus TDD, pas les tests généraux.
Alex