зацикливание в модульном тесте плохо? - PullRequest
3 голосов
/ 24 января 2010

У меня есть юнит-тест, основанный на случайном броске костей. Я бросаю 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 ]

6 голосов
/ 24 января 2010

Проблема этого подхода в том, что вы полагаетесь на случайное поведение. Существует вероятность того, что в течение 300 бросков требуемое состояние никогда не появится, и модульное тестирование не будет выполнено без ошибочного проверенного кода.

Я хотел бы изучить извлечение логики броска костей из класса Dice через интерфейс (например, "IDiceRoller"). Затем вы можете внедрить ролик случайных игральных костей в ваше приложение и еще один ролик в ваш проект модульного тестирования. Эта воля всегда может вернуть предопределенное значение. Таким образом, вы можете написать тесты для конкретных значений костей, не прибегая к циклу и не надеясь, что значение появится.

Пример:

(код в вашей заявке)

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, бросить и проверить это поведение:

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

Dice.Roll(1, 20);
Assert.IsTrue(Dice.IsCritical);
2 голосов
/ 24 января 2010

Хотя тест делает именно то, что я хочу, чтобы я не могу помочь, но чувствую, что Я делаю что-то не так.

Ваши инстинкты верны. Невозможно математически гарантировать, что вы бросите 20, независимо от того, сколько бросков вы делаете. Хотя вероятностно это, скорее всего, произойдет, оно не является хорошим модульным тестом.

Вместо этого проведите модульный тест, который подтвердит, что критический удар зарегистрирован, если IFF (если-и-только-если) выпадет 20.

Возможно, вы также захотите убедиться, что ваш генератор случайных чисел дает вам хорошее распределение, но это еще один модульный тест.

0 голосов
/ 24 января 2010

А теперь противоположное мнение:

Вероятность получения 20 на 300 бросков составляет 1 к 5 миллионам. Однажды ваш модульный тест может не пройти из-за этого, но он пройдет в следующий раз, когда вы его протестируете, даже если вы не касаетесь какого-либо кода.

Суть в том, что ваш тест, вероятно, никогда не провалится из-за полоски неудач, и если да, то что? Усилия, которые вы бы потратили на усиление этого контрольного примера, вероятно, лучше потратить на какую-то другую часть вашего проекта. Если вы хотите быть более параноиком, не усложняя свой тест, измените его на 400 бросков (вероятность неудачи: 1 на 814 миллионов).

...