ランダム性をテストするにはどうすればよいですか?


127

配列内の要素をランダムにシャッフルする方法を検討してください。これが機能していることを確認するために、シンプルでありながら堅牢な単体テストをどのように作成しますか?

私は2つのアイデアを思いつきましたが、どちらにも顕著な欠陥があります:

  • 配列をシャッフルし、順序が以前と異なることを確認します。これは良いように聞こえますが、シャッフルが同じ順序でシャッフルされると失敗します。(ありえませんが、可能です。)
  • 一定のシードで配列をシャッフルし、所定の出力に対してチェックします。これは、同じシードが与えられると常に同じ値を返すランダム関数に依存しています。ただし、これは無効な仮定である場合があります

サイコロの出目をシミュレートし、乱数を返す2番目の関数を考えます。この機能をどのようにテストしますか?その機能をどのようにテストしますか...

  • 与えられた範囲外の数値を返すことはありませんか?
  • 有効な分布で数値を返しますか?(1つのダイスで均一、多数のダイスで通常)。

これらの例だけでなく、一般的なコードのランダム要素をテストするための洞察を提供する回答を探しています。ユニットテストはここでも正しい解決策ですか?そうでない場合、どのようなテストがありますか?


みんなの心を楽にするためだけに、私は自分の乱数ジェネレーターを書いていません


35
タイトカップリングは、その頭を示しています。乱数を生成するオブジェクトを渡します。次に、テスト中に、シャッフル後にデッキがどのように見えるかを知っている指定された数のセットを生成するオブジェクトを渡すことができます。乱数ジェネレーターのランダム性を個別にテストできます。
マーティンヨーク

1
シャッフル(java Collections.shuffle()など)に既存のライブラリルーチンを使用することを強く検討します。developer.com/tech/article.php/616221/には、欠陥のあるシャッフルアルゴリズムの記述に関する注意書きがあります。d6()関数を記述するために、範囲外の数を生成しないと確信できるほど十分にテストし、分布に対してカイ2乗検定を実行します(カイ2乗は擬似ランダムシーケンスにかなり敏感です)。シリアル相関係数も見てください。

「これは、同じシードが与えられると常に同じ値を返すランダム関数に依存しています。ただし、これは無効な仮定である場合があります。」 リンクをたどりましたが、無効な仮定は見当たりません。「同じシードが繰り返し使用される場合、同じ一連の数値が生成されます。」
キラレッサ

@Kyralessa「Randomクラスの乱数ジェネレーターの実装は、.NET Frameworkのメジャーバージョン間で同じままであることが保証されていません。」それほど大きな懸念ではありませんが、まだ考慮すべきことがあります。
dlras2

4
@Kyralessa私はその引用の重要な半分を見逃しました:「結果として、アプリケーションコードは、同じシードが.NET Frameworkの異なるバージョンで同じ擬似ランダムシーケンスをもたらすと仮定するべきではありません。」
dlras2

回答:


102

ユニットテストはランダム性をテストするための適切なツールではないと思います。単体テストでは、メソッドを呼び出して、戻り値(またはオブジェクトの状態)を期待値に対してテストする必要があります。ランダム性をテストする際の問題は、テストしたいもののほとんどに期待される値がないことです。特定のシードを使用してテストできますが、テストできるのは再現性のみです。分布がどれほどランダムである、またはまったくランダムであるかどうかを測定する方法はありません。

幸いなことに、Diehard Battery of Tests of Randomnessなど、実行できる統計的テストがたくさんあります。こちらもご覧ください:

  1. 擬似乱数ジェネレーターを単体テストする方法は?

    • Steve Jessopは、使用しているのと同じRNGアルゴリズムのテスト済みの実装を見つけて、選択したシードとの出力を独自の実装と比較することをお勧めします。
    • Greg Hewgillは、統計的検定のENTスイートを推奨しています。
    • John D. Cookは、読者にCodeProjectの記事Simple Random Number Generationを紹介します。これには、Donald Knuthの第2巻、半数値アルゴリズムで言及されているKolmogorov-Smirnovテストの実装が含まれています。
    • 数人は、生成された数の分布が均一であることをテストすること、カイ2乗テスト、および平均と標準偏差が期待される範囲内にあることをテストすることを推奨します。(分布だけをテストするだけでは十分ではないことに注意してください。[1,2,3,4,5,6,7,8]は均一な分布ですが、確かにランダムではありません。)
  2. ランダムな結果を返す関数を使用した単体テスト

    • Brian Genisioは、RNGのモックはテストを再現可能にするための1つのオプションであると指摘し、C#サンプルコードを提供します。
    • 繰り返しになりますが、複数の人々が、反復性のために固定シード値を使用し、均一分布、カイ2乗などの簡単なテストを使用することを指摘しています。
  3. 単体テストのランダム性は、本質的に再現性のないものをテストしようとするときに既に触れられている多くの課題について説明するWiki記事です。私が収集した興味深い点は次のとおりです。

    以前、値のファイルのランダム性を測定するツールとしてwinzipが使用されていました(明らかに、ファイルを小さくできるほど、ファイルのランダム性は低くなります)。


統計的ランダム性のもう1つの優れたテストスイートは、fourmilab.ch / randomにある「ent」です

1
回答を完全にするために、投稿したリンクの一部を要約できますか?
dlras2

@DanRasmussen確かに、週末にそれをする時間があるでしょう。
トカゲのビル

4
「…ランダム性の問題は、期待値がないことです…」–「期待値」が統計で明確に定義された用語であることを考えると、皮肉なことです。そして、これはあなたが意図したものではありませんが、正しい解決策を示唆しています:統計分布の既知の特性をランダムサンプリングと統計テストと組み合わせて使用​​して、アルゴリズムが非常に高い確率で機能するかどうかを判断します。はい、それは古典的な単体テストではありませんが、最も簡単な場合は期待値の分布を調べるだけなので、言及したかったのです
コンラッドルドルフ

2
Dieharderには有名なDiehard Battery of Tests of Randomnessの最新バージョンがあり、これには米国国立標準技術研究所(NIST)が開発したStatistics Test Suite(STS)が含まれています。:それは、Ubuntuの中にすぐに実行可能ですし、おそらく他のディストリビューションphy.duke.edu/~rgb/General/dieharder.php
nealmcb

21

1.アルゴリズムのユニットテスト

最初の質問では、アルゴリズムの結果がわかっている一連の乱数をフィードする偽のクラスを作成します。そうすれば、ランダム関数のに構築たアルゴリズムが機能することを確認できます。したがって、次のようなものがあります。

Random r = new RandomStub([1,3,5,3,1,2]);
r.random(); //returns 1
r.random(); //returns 3
...

2.ランダム関数が意味をなすかどうかを確認します

単体テストに、複数回実行し、結果をアサートするテストを追加する必要があります

  • 設定した境界内にあります(したがって、サイコロの出目は1〜6です)。
  • 合理的な分布を示します(複数のテストを実行し、分布が期待したもののx%以内にあるかどうかを確認します。たとえば、サイコロの場合2、10%から20%(1/6 = 16.67%) 1000回ロールした場合)。

3.アルゴリズムとランダム関数の統合テスト

配列が元のソートでソートされる頻度はどれくらいですか?数百回並べ替えて、並べ替えが変わらないのは時間のx%だけであると断言します。

これは実際には既に統合テストであり、ランダム関数と一緒にアルゴリズムをテストしています。実際のランダム関数を使用すると、1回のテスト実行でもう逃げられなくなります。

経験から(私は遺伝的アルゴリズムを書いた)、私はあなたのアルゴリズムのユニットテスト、あなたのランダム関数の分布テスト、そして統合テストを組み合わせて行くと言うでしょう。


14

忘れられていると思われるPRNGの側面は、そのプロパティのすべてが本質的に統計的であるということです。配列をシャッフルすると、元の配列とは異なる順列が生じるとは期待できません。基本的に、通常のPRNGを使用している場合、保証されるのは、単純なパターンを使用しないこと(できれば)、返される数値のセット間で均等に分布することだけです。

PRNGの適切なテストでは、PRNGを少なくとも100回実行し、出力の分布を確認します(質問の2番目の部分に対する直接的な回答です)。

最初の質問に対する答えはほとんど同じです。{1、2、...、n}で約100回テストを実行し、各要素が各位置にあった回数をカウントします。シャッフル方法が適切であれば、それらはすべてほぼ等しいはずです。

まったく異なる問題は、暗号化グレードのPRNGをテストする方法です。これは、自分が何をしているのか本当にわかっていない限り、おそらく住むべきではない問題です。人々は、ほんの数回の「最適化」または些細な編集で、優れた暗号システムを破壊する(読む:壊滅的な穴を開ける)ことで知られています。

編集:私は質問、トップアンサーと私自身を徹底的に読み直しました。私が主張する点はまだ残っているが、ビル・ザ・リザードの答えは二番目だろう。ユニットテストは本質的にブールです-失敗するか成功するため、この質問に対する答えは定量的であるため、PRNG(またはPRNGを使用する方法)のプロパティが「どれほど良い」かをテストするのには適していません、極ではなく。


1
私はあなたが各要素が各位置にある回数がほぼ等しいはずであると意味すると思います。それらが一貫して正確に等しい場合、何かが非常に間違っています。
10

@octernありがとう、どうやってそれを書いたのかわからない...今まで完全に間違っていた
...-K.Steff

6

これには、ランダム化のテストとランダム化を使用するもののテストの2つの部分があります。

ランダム化のテストは比較的簡単です。乱数ジェネレーターの周期が期待どおりであること(いくつかの種類のランダムシードを使用したいくつかのサンプルで、あるしきい値内)、および大きなサンプルサイズでの出力の分布が予想どおりであることを確認します。それがある(あるしきい値内)。

ランダム化を使用するもののテストは、決定論的な擬似乱数ジェネレーターを使用して行うのが最適です。ランダム化の出力はシード(その入力)に基づいて既知であるため、入力と予想される出力に基づいて通常どおり単体テストを実行できます。RNGが決定論的でない場合は、決定論的(または単にランダムではない)ものでモックします。ランダム化を使用するコードから隔離して、ランダム化をテストします。


6

何回も実行して、データ視覚化します

Coding Horrorのシャッフルの例を次に示します。アルゴリズムが正常かどうかを確認できます。

ここに画像の説明を入力してください

考えられるすべてのアイテムが少なくとも1回は返され(境界はOK)、配布はOKであることが簡単にわかります。


1
+1の視覚化が重要です。ブロック暗号の記事の ECBセクションにペンギンの写真がある例はいつも好きでした)。自動化されたソフトウェアがそのような規則性を検出できることはめったにありません
-Maksee

え?その視覚化のポイントは、分布が正常ではないことを示すことです。ナイーブシャッフルアルゴリズムは、特定の順序を他の順序よりもはるかに高くします。2341、2314、2143、1342のバーがさらに右に伸びていることに注目してください。
hvd

4

ランダム化された入力を受け取るコードを扱うときに役立つ一般的なポインター:予期されるランダム性のエッジケースをチェックします(最大値と最小値、および該当する場合は最大+1と最小-1の値)。数値に変曲点がある場所(上、上、下)を確認します(例:-1、0、1、または1より大きい、1未満、小数値が関数を台無しにする可能性がある場合は非負)。許可された入力の完全に外側のいくつかの場所を確認します。いくつかの典型的なケースを確認してください。ランダム入力を追加することもできますが、テストを実行するたびに同じ値がテストされないという望ましくない副作用がある単体テストの場合(シードアプローチは機能しますが、シードから最初の1,000個の乱数をテストしますSまたはsomesuch)。

ランダム関数の出力をテストするには、目標を特定することが重要です。カードの場合、0-1ランダムジェネレーターの均一性をテストし、結果に52枚すべてのカードが表示されるかどうかを判断するのが目標か、それとも他の目標(このリストのすべてなど)が目標ですか?

特定の例では、乱数ジェネレーターが不透明であると仮定する必要があります(OSを記述しない限り、OSのsyscallまたはmalloc-を単体テストする意味がないのと同様です)。乱数ジェネレーターを測定することは有用かもしれませんが、目標はランダムジェネレーターを書くことではなく、毎回52枚のカードを受け取り、順番が変わることを確認することです。

これは、実際には2つのテストタスクがあることを示す長い言い方です。RNGが適切なディストリビューションを生成していることをテストし、カードシャッフルコードがランダム化された結果を生成するためにそのRNGを使用していることを確認します。RNGを作成している場合は、統計分析を使用して分布を証明します。カードシャッフルを作成している場合は、各出力に52の非繰り返しカードがあることを確認します(使用していることを検査してテストする方が適切です) RNG)。


4

安全な乱数ジェネレーターを利用できます

私は恐ろしい考えを持っていた:あなたはあなた自身の乱数ジェネレータを書いているのではないのですか?

そうでないと仮定すると、他の人のコード(フレームワークの実装など)ではなく、自分が担当するコードをテストする必要がありSecureRandomます。

コードをテストする

コードが正しく応答することをテストするには、通常、ユニットテストクラスで簡単にオーバーライドできるように、視認性の低いメソッドを使用して乱数を生成します。このオーバーライドされたメソッドは、乱数ジェネレーターを効果的にモックアウトし、何がいつ生成されるかを完全に制御します。その結果、単体テストの目標であるコードを完全に実行できます。

明らかに、エッジ条件をチェックし、適切な入力が与えられたときにアルゴリズムが指示するとおりにシャッフルが行われることを確認します。

安全な乱数ジェネレーターのテスト

使用している言語の安全な乱数ジェネレーターが真にランダムではないか、バグがある(範囲外の値などを提供する)かどうか不明な場合は、数億回の繰り返しにわたって出力の詳細な統計分析を実行する必要があります。各数値の出現頻度をプロットすると、同じ確率で表示されるはずです。結果に何らかの偏りがある場合は、フレームワーク設計者に結果を報告する必要があります。安全な乱数ジェネレータは多くの暗号化アルゴリズムの基本であるため、彼らは間違いなく問題の修正に興味を持っています。


1

まあ、あなたは100%確実ではないので、あなたができる最善のことは、数字がランダムである可能性が高いということです。確率を選ぶ-数百万のサンプルが与えられた場合、エラーの範囲内で数値またはアイテムのサンプルがx回現れると言います。100万回実行して、それがマージン内にあるかどうかを確認します。幸いなことに、コンピューターはこの種のことを簡単にします。


しかし、このような単体テストはグッドプラクティスと見なされますか?ユニットテストはできる限りシンプルにすべきだと常に考えてきました。ループや分岐など、回避できるものはありません。
dlras2

4
単体テストは正しいはずです。分岐、ループ、再帰が必要な場合-それが価格です。ワンライナー単体テストでは、非常に高度で最適化されたクラスを単体テストすることはできません。クラスを1回単体テストするために、ダイクストラのアルゴリズムを実装しました。
K.ステフ

3
@ K.Steff、すごい。ダイクストラアルゴリズムが正しいことを確認するために、単体テストを単体テストしましたか?
ウィンストンイーバート

実際のところ、良い点です-はい、しかし今回は「簡単な」テストです。ただし、元のプログラム(A *)の単体テストでもありました。それは本当に良い習慣だと思います-高速アルゴリズムをテストすることは、不十分な(しかし正しい)実装に対してです。
-K.ステフ

1

乱数のソースが少なくともランダムな外観を持つものを生成していることをテストするには、テストでかなり大きなバイトシーケンスを生成し、それらを一時ファイルに書き込み、Fourmilabのentツールにシェルアウトします。entに-t(簡潔)スイッチを指定すると、解析しやすいCSVが生成されます。次に、さまざまな数値をチェックして、それらが「良い」ことを確認します。

どの数値が適切かを判断するには、既知のランダム性のソースを使用してテストを調整します。乱数の適切なセットが与えられた場合、テストはほぼ常に合格するはずです。真にランダムなシーケンスでさえ、ランダムではないように見えるシーケンスを生成する可能性があるため、確実に合格するテストを取得することはできません。ランダムなシーケンスによってテストが失敗する可能性が低いしきい値を選択するだけです。ランダムネスは面白くないですか?

注:PRNGが「ランダム」シーケンスを生成することを示すテストを作成することはできません。合格した場合、PRNGによって生成されたシーケンスが「ランダム」である確率を示すテストのみを記述できます。ランダムの喜びへようこそ!


1

ケース1:シャッフルのテスト:

配列[0、1、2、3、4、5]を考えて、シャッフルします。何がうまくいかないのでしょうか?通常のもの:a)シャッフルなし、b)1-5をシャッフル、0ではなく、0-4でシャッフル、5ではなくシャッフル、常に同じパターンを生成、...

すべてをキャッチする1つのテスト:

100回シャッフルし、各スロットに値を追加します。各スロットの合計は、他の各スロットと類似している必要があります。Avg / Stddevを計算できます。(5 + 0)/2=2.5、100*2.5 =25。たとえば、期待値は約25です。

値が範囲外の場合、わずかな可能性があり、偽陰性が発生する可能性があります。その可能性がどれだけ大きいかを計算できます。テストを繰り返します。まあ-もちろん、テストが連続して2回失敗する可能性はわずかです。しかし、ユニットテストが失敗した場合、ソースを自動的に削除するルーチンはありませんか?もう一度実行してください!

連続して3回失敗しますか?宝くじで運を試してみてください。

ケース2:サイコロを振る

サイコロの質問は同じ質問です。サイコロを6000回投げます。

for (i in 0 to 6000) 
    ++slot [Random.nextInt (6)];
return (slot.max - slot.min) < threshold;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.