Dans TDD, si j'écris un scénario de test qui réussit sans modifier le code de production, qu'est-ce que cela signifie?

17

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:

  1. Est-ce à dire que j'ai fait quelque chose de mal?
  2. Dois-je éviter d'écrire de tels tests à l'avenir si cela peut être aidé?
  3. 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.

Daniel Kaplan
la source
Le "Bowling Game Kata" lié à l'article que vous citez a en fait un test de réussite immédiate comme étape finale.
jscs

Réponses:

21

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à.

pdr
la source
L'amélioration de la couverture de test du code existant est une autre raison parfaitement valable pour écrire un test de réussite (espérons-le).
Jack
13

Cela signifie que:

  1. Vous avez écrit le code de production qui remplit la fonction que vous souhaitez sans avoir d'abord écrit le test (une violation du "TDD religieux"), ou
  2. La fonctionnalité dont vous avez besoin est déjà remplie par le code de production, et vous écrivez simplement un autre test unitaire pour couvrir cette fonctionnalité.

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):

public void TestAddMethod()
{
    Assert.IsTrue(Add(2,3) == 5);
}

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:

public int add(int x, int y)
{
    return x + y;
}

Mais disons que je dois maintenant ajouter 4 et 6 ensemble:

public void TestAddMethod2()
{
    Assert.IsTrue(Add(4,6) == 10);
}

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:

public void TestAddMethod3()
{
    Assert.IsTrue(Add(100,100) == 100);
}

Et ce test va maintenant échouer. Je dois maintenant réécrire ma fonction

public int add(int x, int y)
{
    var a = x + y;
    return a > 100 ? 100 : a;
}

pour le faire passer.

Le bon sens veut que si

public void TestAddMethod2()
{
    Assert.IsTrue(Add(4,6) == 10);
}

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.

Robert Harvey
la source
5
Si vous suiviez complètement les exemples de Martin (et il ne suggère pas nécessairement que vous le fassiez), pour add(2,3)réussir, vous retourneriez littéralement 5. Codé en dur. Ensuite, vous écririez le test add(4,6)qui vous obligerait à écrire le code de production qui le fait passer sans se casser add(2,3)en même temps. Tu finirais avec return 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.
Anthony Pegram
1
@tieTYT, généralement, si je me souviens bien des livres de Martin, le deuxième cas de test serait généralement suffisant pour vous permettre d'écrire la solution générale d'une méthode simple (et, en réalité, vous le feriez en effet première fois). Pas besoin d'un troisième.
Anthony Pegram
2
@tieTYT, vous continueriez à écrire des tests jusqu'à ce que vous le fassiez. :)
Anthony Pegram
4
Il y a une troisième possibilité, et cela va à l'encontre de votre exemple: vous avez écrit un test en double. Si vous suivez TDD "religieusement", alors un nouveau test qui passe est donc toujours un drapeau rouge. Après DRY , vous ne devez jamais écrire deux tests qui testent essentiellement la même chose.
congusbongus
1
"Si vous suiviez complètement les exemples de Martin (et il ne suggère pas nécessairement que vous le fassiez), pour faire passer add (2,3), vous retourneriez littéralement 5. Codé en dur." - c'est le peu de TDD strict qui m'a toujours grogné, l'idée que vous écrivez du code que vous savez être faux dans l'attente d'un futur test qui viendra et le prouvera. Que se passe-t-il si ce futur test n'est jamais écrit, pour une raison quelconque, et que mes collègues supposent que «tous les tests verts» implique «tous les codes corrects»?
Julia Hayward
2

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 (

9dan
la source
2

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.

leifbattermann
la source
0

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:

assert abs(-88888) == 88888
assert abs(-12345) == 12345
assert abs(-5000) == 5000
assert abs(-32) == 32
assert abs(46) == 46
assert abs(50) == 50
assert abs(5001) == 5001
assert abs(999999) == 999999
...

Le deuxième programmeur écrit:

assert abs(-1) == 1
assert abs(0) == 0
assert abs(1) == 1

L'implémentation du premier programmeur peut entraîner:

def abs(n):
    if n < 0:
        return -n
    elif n > 0:
        return n

L'implémentation du deuxième programmeur peut entraîner:

def abs(n):
    if n < 0:
        return -n
    else:
        return n

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.

réflexion
la source
Eh bien, le deuxième programmeur était clairement insouciant avec les tests également, car son collègue a redéfini abs(n) = n*net réussi.
Eiko
@Eiko Vous avez absolument raison. Écrire trop peu de tests peut vous mordre tout autant. Le deuxième programmeur était trop avare en ne testant pas au moins abs(-2). Comme pour tout, la modération est la clé.
réflexion