3

ランダムなサイコロのロールに依存する単体テストがあります。私は 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 クラスには他の情報も含まれていますが、私の質問は特に単体テストでのループに関するものなので、より明確にするために省略しました

4

3 に答える 3

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クラスでモック化されたサイコロ ローラーを設定し、転がしてその動作を確認できます。

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

Dice.Roll(1, 20);
Assert.IsTrue(Dice.IsCritical);
于 2010-01-24T08:02:44.030 に答える
2

テストは私が望んでいることを正確に実行しますが、何か間違ったことをしているように感じずにはいられません。

あなたの本能は正しいです。何回ロールしても 20 が出ることを数学的に保証する方法はありません。確率論的にはこれが起こる可能性が最も高いですが、それは良い単体テストにはなりません。

代わりに、クリティカル ストライクが IFF (if-and-only-if) で 20 がロールされることを確認する単体テストを作成します。

おそらく、乱数ジェネレーターが適切な分布を提供することも確認する必要がありますが、それは別の単体テストです。

于 2010-01-24T07:58:50.523 に答える
0

そして今、反対の見解:

300 回のロールで 20 が出ない確率は、約 500 万分の 1 です。いつかそれが原因で単体テストが通らなくなるかもしれませんが、コードに何も触れなくても、次にテストするときには通ります。

私の言いたいことは、一連の不運が原因でテストが失敗することはおそらくないということです。このテスト ケースを強化するために費やす労力は、おそらくプロジェクトの他の部分に費やす方がよいでしょう。テストをより複雑にせずにもっと偏執的になりたい場合は、400 ロールに変更します (失敗の確率: 8 億 1400 万分の 1)。

于 2010-01-24T08:28:31.810 に答える