Voici les règles de Robert C. Martin pour TDD :
- Vous n'êtes pas autorisé à écrire un code de production, sauf pour effectuer un test unitaire ayant échoué.
- Vous n'êtes pas autorisé à écrire plus d'un test unitaire que ce qui est suffisant pour échouer; et les échecs de compilation sont des échecs.
- Vous n'êtes pas autorisé à écrire plus de code de production qu'il n'en faut pour réussir le test unitaire ayant échoué.
Lorsque j'écris un test qui semble utile mais qui passe sans changer le code de production:
- Est-ce à dire que j'ai fait quelque chose de mal?
- Dois-je éviter d'écrire de tels tests à l'avenir si cela peut être aidé?
- Dois-je laisser ce test là ou le supprimer?
Note: j'essaie de poser cette question ici: Puis - je commencer par un test unitaire qui passe? Mais je n'ai pas été en mesure de formuler la question suffisamment bien jusqu'à présent.
testing
unit-testing
tdd
Daniel Kaplan
la source
la source
Réponses:
Il dit que vous ne pouvez pas écrire de code de production à moins que ce ne soit pour réussir un test unitaire qui échoue, pas que vous ne pouvez pas écrire un test qui réussit dès le départ. L'intention de la règle est de dire "Si vous devez modifier le code de production, assurez-vous que vous écrivez ou modifiez un test pour lui en premier."
Parfois, nous écrivons des tests pour prouver une théorie. Le test réussit et cela réfute notre théorie. Nous ne supprimons pas ensuite le test. Cependant, nous pourrions (sachant que nous avons le soutien du contrôle de code source) casser le code de production, pour nous assurer que nous comprenons pourquoi il est passé alors que nous ne nous y attendions pas.
S'il s'avère être un test valide et correct et qu'il ne reproduit pas un test existant, laissez-le là.
la source
Cela signifie que:
Cette dernière situation est plus courante que vous ne le pensez. À titre d'exemple complètement spécieux et trivial (mais toujours illustratif), disons que vous avez écrit le test unitaire suivant (pseudocode, parce que je suis paresseux):
Parce que tout ce dont vous avez vraiment besoin est le résultat de 2 et 3 additionnés.
Votre méthode de mise en œuvre serait:
Mais disons que je dois maintenant ajouter 4 et 6 ensemble:
Je n'ai pas besoin de réécrire ma méthode, car elle couvre déjà le deuxième cas.
Maintenant, disons que j'ai découvert que ma fonction Ajouter a vraiment besoin de renvoyer un nombre qui a un certain plafond, disons 100. Je peux écrire une nouvelle méthode qui teste ceci:
Et ce test va maintenant échouer. Je dois maintenant réécrire ma fonction
pour le faire passer.
Le bon sens veut que si
réussit, vous ne faites pas délibérément échouer votre méthode juste pour que vous puissiez avoir un test qui échoue afin que vous puissiez écrire du nouveau code pour réussir ce test.
la source
add(2,3)
réussir, vous retourneriez littéralement 5. Codé en dur. Ensuite, vous écririez le testadd(4,6)
qui vous obligerait à écrire le code de production qui le fait passer sans se casseradd(2,3)
en même temps. Tu finirais avecreturn x + y
, mais tu ne commencerais pas avec. En théorie. Naturellement, Martin (ou peut-être que c'était quelqu'un d'autre, je ne me souviens pas) aime fournir de tels exemples pour l'éducation, mais ne s'attend pas à ce que vous écriviez réellement un code aussi trivial de cette façon.Votre test réussit mais vous ne vous trompez pas. Je pense que c'est arrivé parce que le code de production n'est pas TDD depuis le début.
Supposons TDD canonique (?). Il n'y a pas de code de production mais quelques cas de test (c'est bien sûr toujours un échec). Nous ajoutons du code de production à passer. Ensuite, arrêtez-vous ici pour ajouter d'autres cas de test d'échec. Ajoutez à nouveau le code de production à passer.
En d'autres termes, votre test pourrait être une sorte de test de fonctionnalité et non un simple test unitaire TDD. Ce sont toujours des atouts précieux pour la qualité du produit.
Personnellement, je n'aime pas de telles règles totalitaires et inhumaines (
la source
En fait, le même problème s'est posé hier soir dans un dojo.
J'ai fait une recherche rapide à ce sujet. Voici ce que j'ai trouvé:
Fondamentalement, il n'est pas interdit explicitement par les règles TDD. Peut-être que des tests supplémentaires sont nécessaires pour prouver qu'une fonction fonctionne correctement pour une entrée généralisée. Dans ce cas, la pratique TDD est laissée de côté pendant un petit moment. Notez que quitter la pratique TDD sous peu n'est pas nécessairement enfreindre les règles TDD tant qu'il n'y a pas de code de production ajouté entre-temps.
Des tests supplémentaires peuvent être écrits tant qu'ils ne sont pas redondants. Une bonne pratique serait de faire des tests de partitionnement de classe d'équivalence. Cela signifie que les cas de bord et au moins un cas intérieur pour chaque classe d'équivalence sont testés.
Un problème qui pourrait se produire avec cette approche, cependant, est que si les tests réussissent depuis le début, il ne peut être assuré qu'il n'y a pas de faux positifs. Cela signifie qu'il peut y avoir des tests qui passent parce que les tests ne sont pas implémentés correctement et non parce que le code de production fonctionne correctement. Pour éviter cela, le code de production doit être légèrement modifié pour casser le test. Si cela fait échouer le test, le test est très probablement correctement implémenté et le code de production peut être modifié pour que le test réussisse à nouveau.
Si vous voulez simplement pratiquer le TDD strict, vous pourriez ne pas écrire de tests supplémentaires réussis depuis le début. D'un autre côté, dans un environnement de développement d'entreprise, on devrait en fait quitter la pratique TDD si des tests supplémentaires semblent utiles.
la source
Un test qui réussit sans modifier le code de production n'est pas intrinsèquement mauvais et est souvent nécessaire pour décrire une exigence supplémentaire ou un cas limite. Tant que votre test "vous semble utile", comme vous le dites, gardez-le.
Lorsque vous rencontrez des problèmes, c'est lorsque vous écrivez un test déjà réussi en remplacement de la compréhension réelle de l'espace du problème.
On peut imaginer à deux extrêmes: un programmeur qui écrit un grand nombre de tests "juste au cas où" on attrape un bug; et un deuxième programmeur qui analyse soigneusement l'espace du problème avant d'écrire un nombre minimal de tests. Disons que les deux tentent d'implémenter une fonction de valeur absolue.
Le premier programmeur écrit:
Le deuxième programmeur écrit:
L'implémentation du premier programmeur peut entraîner:
L'implémentation du deuxième programmeur peut entraîner:
Tous les tests réussissent, mais le premier programmeur a non seulement écrit plusieurs tests redondants (ralentissant inutilement leur cycle de développement), mais a également échoué à tester un cas limite (
abs(0)
).Si vous vous retrouvez à écrire des tests qui réussissent sans modifier le code de production, demandez-vous si vos tests ajoutent vraiment de la valeur ou si vous devez passer plus de temps à comprendre l'espace du problème.
la source
abs(n) = n*n
et réussi.abs(-2)
. Comme pour tout, la modération est la clé.