2010-01-24 6 views
3

J'ai un test unitaire qui repose sur un jet de dés aléatoires. Je lance un dé de 20 faces et si la valeur est 20 cela compte comme un coup critique.boucle dans test unité mauvaise?

Ce que je fais droit est maintenant rouler le 20 dégrossi meurent jusqu'à 300 fois. Si l'un de ces jets est un 20 alors je sais que j'ai eu un coup critique.

Voici ce que le code ressemble à:

public class DiceRoll 
{ 
    public int Value { get; set; } 
    public bool IsCritical { get; set; } 

    // code here that sets IsCritical to true if "Value" gets set to 20 
} 

[Test] 
public void DiceCanRollCriticalStrikes() 
{ 
    bool IsSuccessful = false; 
    DiceRoll diceRoll = new DiceRoll(); 

    for(int i=0; i<300; i++) 
    { 
     diceRoll.Value = Dice.Roll(1, 20); // roll 20 sided die once 
     if(diceRoll.Value == 20 && diceRoll.IsCritical) 
     { 
      IsSuccessful = true; 
      break; 
     } 
    } 

    if(IsSuccessful) 
     // test passed 
    else 
     // test failed 
} 

Bien que le test fait exactement ce que je veux qu'il Je ne peux pas aider mais se sentir comme si je fais quelque chose de mal.

Sur le même sujet, la classe DiceRoll a d'autres informations aussi bien, mais ma question est spécifiquement sur la boucle dans un test unitaire, donc je l'ai laissé sortir pour le rendre plus clair

Répondre

6

Le problème avec cette approche est que vous vous reposez sur un comportement aléatoire. Il est possible que dans les 300 rouleaux, l'état voulu n'apparaisse jamais et que le test unitaire échoue, sans que le code testé ne soit erroné.

je regarderais en extraire la logique de lancer de dés à partir de la classe dés par une interface (« IDiceRoller », par exemple). Ensuite, vous pouvez implémenter le rouleau de dés aléatoire dans votre application, et un autre rouleau de dés dans votre projet de test unitaire. Celui-ci pourra toujours renvoyer une valeur prédéfinie. De cette façon, vous pouvez écrire des tests pour des valeurs de dés spécifiques sans devoir recourir à la boucle et espérer que la valeur apparaisse.

Exemple:

(code dans votre application)

public interface IDiceRoller 
{ 
    int GetValue(int lowerBound, int upperBound); 
} 

public class DefaultRoller : IDiceRoller 
{ 
    public int GetValue(int lowerBound, int upperBound) 
    { 
     // return random value between lowerBound and upperBound 
    } 
} 

public class Dice 
{ 
    private static IDiceRoller _diceRoller = new DefaultRoller(); 

    public static void SetDiceRoller(IDiceRoller diceRoller) 
    { 
     _diceRoller = diceRoller; 
    } 

    public static void Roll(int lowerBound, int upperBound) 
    { 
     int newValue = _diceRoller.GetValue(lowerBound, upperBound); 
     // use newValue 
    } 
} 

... et dans votre unité projet de test:

internal class MockedDiceRoller : IDiceRoller 
{ 
    public int Value { get; set; } 

    public int GetValue(int lowerBound, int upperBound) 
    { 
     return this.Value; 
    } 
} 

Maintenant, dans votre test unitaire, vous pouvez créer un MockedDiceRoller, définissez la valeur que vous voulez que les dés pour obtenir, régler le rouleau dés moqué dans la classe Dice, rouleau et vérifier que le comportement:

MockedDiceRoller diceRoller = new MockedDiceRoller(); 
diceRoller.Value = 20; 
Dice.SetDiceRoller(diceRoller); 

Dice.Roll(1, 20); 
Assert.IsTrue(Dice.IsCritical); 
+0

+1 Exactement ce que j'étais en train d'écrire. – APC

+3

Rappelle-moi de la bande dessinée xkcd: "int rolldice() {return 4;} // généré par le dé réel, garanti d'être aléatoire". –

+0

+1 pour un exemple gentil/propre. –

2

Bien que le test fait exactement ce que je veux Je ne peux pas m'empêcher de ressentir Je fais quelque chose de mal.

Vos instincts sont corrects. Il n'y a aucun moyen mathématique de s'assurer que vous obtiendrez un 20, quel que soit le nombre de lancers. Bien que cela se produise probabilistically, il ne fait pas un bon test unitaire.

Au lieu de cela, faire un test unitaire qui vérifie qu'une grève critique est enregistrée IFF (si et seulement si vous êtes) 20 est enroulé.

Vous aurez probablement envie de vérifier que votre générateur de nombres aléatoires vous donne une bonne distribution, mais c'est un autre test unitaire.

+0

Mais est-ce que je le fais toujours en boucle? Ou est-ce que je reconcevois ma classe de dés pour que je puisse forcer mes propres résultats? Par exemple, même vérifier IFF a 20 signifie que je dois boucler 300 fois pour vérifier qu'un "aléatoire" 20 a été lancé. – mikedev

+0

Non, deux classes de test unitaires différentes. Un test unitaire pour la classe DiceRoll et la relation entre les propriétés Value et IsCritical. Un autre test unitaire pour la méthode Roll de la classe Dice qui appelle la boucle plusieurs fois (10K? Peut-être plus?) Et vérifie qu'il vous donne un niveau acceptable de "hasard". Si vous ne faites qu'encapsuler la classe .NET Random, vous ne pouvez pas écrire le test Dice.Roll. – micahtan

0

Et maintenant, un point de vue opposé:

Les chances de ne pas obtenir un 20 à 300 rouleaux est d'environ 1 à 5 millions. Un jour, votre test unitaire pourrait ne pas réussir à cause de cela, mais il passera la prochaine fois que vous le testerez même si vous ne touchez aucun code.Mon point est que votre test n'échouera probablement jamais en raison d'une série de malchance, et si oui, et alors? L'effort que vous déploieriez pour étoffer ce cas de test est probablement mieux passé sur une autre partie de votre projet. Si vous voulez être plus paranoïaque sans compliquer votre test, changez-le à 400 rouleaux (chances d'échec: 1 sur 814 millions).

+0

Je suis d'accord avec la notion de 1 sur 5 millions. Mon problème avec le test original est qu'il teste deux choses différentes: 1) Ce 20 = coup critique. 2) Que les dés finiront par arriver 20. Vous n'avez pas besoin de faire 300 itérations pour tester # 1, et selon la façon dont # 2 est implémenté, vous ne pouvez probablement même pas écrire ce test. – micahtan

+0

Même si je suis d'accord avec ce que vous dites, la raison pour laquelle je pose cette question est d'en apprendre davantage sur les tests unitaires appropriés. – mikedev