Quelle est la meilleure ligne de conduite dans TDD si, après avoir correctement implémenté la logique, le test échoue toujours (car il y a une erreur dans le test)?
Par exemple, supposons que vous souhaitiez développer la fonction suivante:
int add(int a, int b) {
return a + b;
}
Supposons que nous le développions dans les étapes suivantes:
Test d'écriture (pas encore de fonction):
// test1 Assert.assertEquals(5, add(2, 3));
Résultats en erreur de compilation.
Écrivez une implémentation de fonction factice:
int add(int a, int b) { return 5; }
Résultat:
test1
passe.Ajoutez un autre scénario de test:
// test2 -- notice the wrong expected value (should be 11)! Assert.assertEquals(12, add(5, 6));
Résultat:
test2
échoue,test1
passe toujours.Écrivez une implémentation réelle:
int add(int a, int b) { return a + b; }
Résultat:
test1
passetest2
toujours, échoue toujours (depuis11 != 12
).
Dans ce cas particulier: serait-il préférable de:
- correct
test2
, et voyez qu'il passe maintenant, ou - supprimez la nouvelle partie de l'implémentation (c.-à-d. revenez à l'étape 2 ci-dessus), corrigez
test2
et laissez-la échouer, puis réintroduisez l'implémentation correcte (étape 4 ci-dessus).
Ou existe-t-il un autre moyen plus intelligent?
Bien que je comprenne que l'exemple du problème est plutôt trivial, je suis intéressé par ce qu'il faut faire dans le cas générique, qui pourrait être plus complexe que l'ajout de deux nombres.
EDIT (En réponse à la réponse de @Thomas Junk):
Le point central de cette question est ce que TDD suggère dans un tel cas, et non ce qui est "la meilleure pratique universelle" pour obtenir un bon code ou des tests (qui pourraient être différents de la méthode TDD).
Réponses:
La chose absolument critique est que vous voyez le test réussir et échouer.
Que vous supprimiez le code pour faire échouer le test, puis le réécriviez ou le glissiez dans le presse-papiers pour le coller plus tard n'a pas d'importance. TDD n'a jamais dit que vous deviez retaper quoi que ce soit. Il veut savoir que le test réussit uniquement lorsqu'il doit réussir et échoue uniquement lorsqu'il doit échouer.
Voir le test réussir et échouer est la façon dont vous testez le test. Ne faites jamais confiance à un test que vous n'avez jamais vu faire les deux.
Refactoring Against The Red Bar nous donne des étapes formelles pour refactoring d'un test de travail:
Cependant, nous ne refactorisons pas un test de travail. Nous devons transformer un test de buggy. Une préoccupation est le code qui a été introduit alors que seul ce test le couvrait. Ce code devrait être annulé et réintroduit une fois le test corrigé.
Si ce n'est pas le cas et que la couverture du code n'est pas un problème en raison d'autres tests couvrant le code, vous pouvez transformer le test et l'introduire en tant que test vert.
Ici, le code est également annulé, mais juste assez pour entraîner l'échec du test. Si cela ne suffit pas pour couvrir tout le code introduit alors qu'il n'est couvert que par le test de buggy, nous avons besoin d'un plus grand retour en arrière du code et de plus de tests.
Introduire un test vert
Briser le code peut être commenter le code ou le déplacer ailleurs pour le coller plus tard. Cela nous montre l'étendue du code couvert par le test.
Pour ces deux dernières courses, vous êtes de retour dans le cycle vert-rouge normal. Vous collez simplement au lieu de taper pour annuler le code et faire passer le test. Assurez-vous donc de ne coller que la quantité suffisante pour réussir le test.
Le motif global ici est de voir la couleur du test changer comme nous l'attendons. Notez que cela crée une situation où vous avez brièvement un test vert non fiable. Faites attention à ne pas être interrompu et à oublier où vous en êtes dans ces étapes.
Mes remerciements à RubberDuck pour le lien Embracing the Red Bar .
la source
Quel est l' objectif global que vous souhaitez atteindre?
Faire de bons tests?
Faire la bonne mise en œuvre?
Faire le TTD religieusement bien ?
Aucune de ces réponses?
Vous pensez peut-être trop votre relation avec les tests et les tests.
Les tests ne garantissent pas l' exactitude d'une implémentation. La réussite de tous les tests ne dit rien sur la question de savoir si votre logiciel fait ce qu'il doit; il ne fait aucune déclaration essentialiste sur votre logiciel.
Prenons votre exemple:
L'implémentation "correcte" de l'addition serait le code équivalent à
a+b
. Et tant que votre code fait cela, vous diriez que l'algorithme est correct dans ce qu'il fait et qu'il est correctement implémenté.À première vue , nous serions tous deux d'accord pour dire qu'il s'agit de la mise en œuvre d'un ajout.
Mais ce que nous faisons n'est pas vraiment de dire que ce code est l'implémentation de
addition
celui - ci ne se comporte qu'à un certain degré comme celui-ci: pensez au débordement d'entier .Le débordement d'entier se produit dans le code, mais pas dans le concept de
addition
. Donc: votre code se comporte dans une certaine mesure comme le concept deaddition
, mais ne l'est pasaddition
.Ce point de vue plutôt philosophique a plusieurs conséquences.
Et on peut dire que les tests ne sont rien de plus que des hypothèses de comportement attendu de votre code. En testant votre code, vous pourriez (peut-être) ne jamais vous assurer que votre implémentation est correcte , le mieux que vous puissiez dire est que vos attentes sur les résultats que votre code fournit ont été ou n'ont pas été satisfaites; que ce soit votre code qui soit faux, que ce soit votre test qui soit faux ou que ce soit les deux tous les deux.
Des tests utiles vous aident à fixer vos attentes sur ce que le code doit faire: tant que je ne change pas mes attentes et tant que le code modifié me donne le résultat que j'attends, je peux être sûr, que les hypothèses que j'ai faites à propos de les résultats semblent fonctionner.
Cela n'aide pas lorsque vous faites de fausses hypothèses; Mais salut! au moins elle prévient la schizophrénie: s'attendre à des résultats différents alors qu'il ne devrait pas y en avoir.
tl; dr
Vos tests sont des hypothèses sur le comportement du code. Si vous avez de bonnes raisons de penser que votre implémentation est correcte, corrigez le test et voyez si cette hypothèse se vérifie.
la source
datatype
c'est clairement le mauvais choix. Un test révélerait que: votre attente serait «fonctionne pour les grands nombres» et n'est dans plusieurs cas pas satisfaite. La question serait alors de savoir comment traiter ces cas. S'agit-il de cas d'angle? Si oui, comment y faire face? Peut-être que certaines clauses quard aident à éviter un plus grand désordre. La réponse est liée au contexte.Vous devez savoir que le test échouera si la mise en œuvre est incorrecte, ce qui n'est pas la même chose que si la mise en œuvre est correcte. Par conséquent, vous devez remettre le code dans un état où vous vous attendez à ce qu'il échoue avant de corriger le test, et vous assurer qu'il échoue pour la raison que vous attendiez (c.-à-d.
5 != 12
), Plutôt que quelque chose d'autre que vous n'aviez pas prévu .la source
assertTrue(5 == add(2, 3))
donne une sortie moins utile queassertEqual(5, add(2, 3))
même s'ils testent tous les deux la même chose).Dans ce cas particulier, si vous changez le 12 en 11, et que le test passe maintenant, je pense que vous avez fait un bon travail pour tester le test ainsi que la mise en œuvre, donc il n'y a pas grand-chose à passer par des cercles supplémentaires.
Cependant, le même problème peut survenir dans des situations plus complexes, comme lorsque vous avez une erreur dans votre code d'installation. Dans ce cas, après avoir corrigé votre test, vous devriez probablement essayer de muter votre implémentation de manière à faire échouer ce test particulier, puis annuler la mutation. Si le retour de l'implémentation est la manière la plus simple de le faire, alors c'est très bien. Dans votre exemple, vous pouvez muter
a + b
ena + a
oua * b
.Alternativement, si vous pouvez muter légèrement l'assertion et voir l'échec du test, cela peut être assez efficace pour tester le test.
la source
Je dirais que c'est un cas pour votre système de contrôle de version préféré:
Organisez la correction du test en conservant vos modifications de code dans votre répertoire de travail.
Validez avec un message correspondant
Fixed test ... to expect correct output
.Avec
git
, cela peut nécessiter l'utilisation degit add -p
si test et implémentation sont dans le même fichier, sinon vous pouvez évidemment simplement mettre les deux fichiers en scène séparément.Validez le code d'implémentation.
Remontez dans le temps pour tester la validation effectuée à l'étape 1, en vous assurant que le test échoue réellement .
Vous voyez, de cette façon, vous ne comptez pas sur vos prouesses d'édition pour déplacer votre code d'implémentation pendant que vous testez votre test d'échec. Vous utilisez votre VCS pour sauvegarder votre travail et pour vous assurer que l'historique enregistré VCS comprend correctement à la fois l'échec et le test de réussite.
la source