循环单元测试不好?

时间:2010-01-24 07:49:05

标签: c# unit-testing loops dice

我有一个依赖随机骰子滚动的单元测试。我滚动了一个20面的骰子,如果它是20,那就算是一个致命的打击。

我现在正在做的是将20面模具滚动300次。如果这些卷中的任何一个是20,那么我知道我受到了重击。

这是代码的样子:

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 
}

虽然测试完全符合我的要求,但我不禁觉得自己做错了。

在相关的说明中,DiceRoll类中还包含其他信息,但我的问题是关于在单元测试中循环,所以我把它留下来以使其更清晰

3 个答案:

答案 0 :(得分:6)

这种方法的问题在于您依赖于随机行为。有可能在300卷内,所需状态永远不会出现,并且单元测试失败,而且测试代码没有错误。

我会考虑通过接口(例如“IDiceRoller”)从Dice类中提取骰子滚动逻辑。然后,您可以在您的应用程序中实施随机骰子滚轮,并在您的单元测试项目中实施另一个骰子滚轮。这个可以始终返回预定义的值。这样,您就可以编写特定骰子值的测试,而无需求助于循环并希望显示该值。

示例:

(申请中的代码)

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
    }
}

......并在您的单元测试项目中:

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

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

现在,在您的单元测试中,您可以创建MockedDiceRoller,设置您希望骰子获得的值,在Dice类中设置模拟骰子滚动,滚动并验证该行为:< / p>

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

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

答案 1 :(得分:2)

  

虽然测试完全符合我的要求   希望它能让我感到忍不住   我做错了。

你的直觉是正确的。无论你做了多少卷,都无法在数学上确保你将滚动20。虽然概率上很可能发生这种情况,但它并不适合进行良好的单元测试。

相反,进行一个单元测试,验证是否已注册一个致命一击IFF(if-and-only-if)一个20被滚动。

您可能还想验证您的随机数生成器是否为您提供了良好的分布,但这是另一个单元测试。

答案 2 :(得分:0)

现在,反对观点:

没有获得20个300卷的可能性大约是五百万分之一。有一天,你的单元测试可能不会因此而过,但即使你没有碰到任何代码,它也会在你下次测试时通过。

我的观点是,你的测试可能永远不会因为运气不好而失败,如果确实如此,那又怎样呢?你花费在这个测试用例上的努力可能会更好地花在项目的其他部分上。如果你想在不使测试更复杂的情况下变得更偏执,可以将其改为400卷(失败的几率:在8.14亿中有1个)。