web-dev-qa-db-ja.com

乱数ジェネレーターを使用する関数のテスト

私が実装しているインターフェースの一部であるメソッドがあります。このメソッドは、乱数ジェネレータを使用して出力を生成し、それを呼び出し側メソッドに返す別のプライベートメソッドを呼び出します。呼び出しメソッドをテストしたい。どうやってやるの?これはテスト中のメソッドです:

 @Override
 public String generate(int wordCount) {
    StringBuilder sentence = new StringBuilder();

    List<String> selectedStrings = selectRandomStringsFromInternalVocabulary(wordCount, new Random());
    selectedStrings.sort(Comparator.<String>naturalOrder());

    swapOddIndexedStringsWithEvenIndexedStrings(selectedStrings);

    for (String Word: selectedStrings)
        sentence.append(Word)
                .append(" ");


    return sentence.toString().trim();
}

これは、乱数ジェネレータを使用する方法です。

private List<String> selectRandomStringsFromInternalVocabulary(int wordCount, Random random) {
    List<String> selectedStrings = new ArrayList<>();
    int wordCountInVocabulary = internalVocabulary.size();

    while (wordCount-- != 0) {
        int stringIndex = random.nextInt(wordCountInVocabulary);
        selectedStrings.add(internalVocabulary.get(stringIndex));
    }

    return selectedStrings;
}

私ができると思ったことがいくつかあります。1. 2番目のメソッドをpackage-privateにしてテストします。しかし、私がそれを避けることができるなら、私はプライベートメソッドをテストしたくないです。 2. Randomをパラメーターとして呼び出し関数に追加し、テスト中にモックを渡します。ただし、インターフェイスの一部とそれを実装する他のクラスはRNGを使用しません。さらに、クライアントに実装の詳細を知らせたくありません。

私はこれらの質問を終えました:1. 不確定な出力を伴うユニットテストメソッド 2. ランダムな動作をする関数のユニットテスト

しかし、提案は上記で述べたものと同様です。

3
sayeed

「私が何をしようとしているのか 証明 ?」(注:「証明」は数学的な意味で使用されますが、単に何かの妥当性をテストするためです)。単体テストは、アプリケーションが設計されたパラメーター内で機能していることをテストするためにあります。正当性のテストが異なると、異なるアプローチが必要になります。

  • 返された文字列のすべての単語がグローバル語彙に存在することを確認できます
  • 返された文字列に単語の繰り返しがあるかどうかを確認できます
  • 最初と最後に余分な空白がないことを確認できます
  • 単語数が正の数を強制していることを確認できます
  • 0ワードのリストが空の文字列を提供していることを確認できます

それで十分かもしれません。これらのタイプのテストもかなり堅牢です。乱数ジェネレーターの実装が変更されても壊れません。内部機能を利用するために、引き続き外部インターフェイスを使用しています。

この場合、文字列の特定のシーケンスをテストすることは、もろく、ほんのわずかしか役に立たないと証明するでしょう。あなたの例では、それほど多くのブランチパスを心配する必要はありません。契約について考え、それが本当に何であるかを考えてランダム性を確保し、受け入れる必要があります


注意すべき重要なことは、ランダム性を証明していないということです。メソッドが期待どおりに動作することを確認しています。

ランダム性をテストする場合、乱数の広がりが期待どおりであることを証明するために、いくつかの統計分析を行う必要があります。これらの種類のテストでは、単語の数学的な意味での「証明」が必要です。単体テストはその仕事に適したツールではありません。

11
Berin Loritsch

依存性注入を使用します。 IRandomNumberGeneratorインターフェイスを作成し、それを必要なクラス(コンストラクターの引数として)または関数(パラメーターとして)に挿入します。次のように簡単にすることができます。

interface IRandomNumberGenerator
{
     int GetRandomNumber();
}

次に、そのインターフェースを実装する2つのクラスを作成します。実数生成クラスとモック。モック実装は事前定義された数を返し、実際の実装は実際の乱数を返します。モック実装でテストします。

class MockRandomNumberGenerator : IRandomNumberGenerator
{
     private int _fakeRandomNumber;

     public MockRandomNumberGenerator(int fakeRandomNumber)
     {
          _fakeRandomNumber = fakeRandomNumber
     }

     public int GetRandomNumber()
     {
          return _fakeRandomNumber;
     }
}

実際の乱数が生成されている状態テストがない場合、その場合、テスト結果は再現できなくなります。 Javaに慣れていないため、C#構文を使用しています。

6
Eternal21

テスト時:

指定した特定のシードで乱数ジェネレータを使用します。次に、常に同じシーケンスを取得します。これにより、テストが可能になります。

3
Pieter B

Randomをテスト中のクラスのメンバーにします。テスト用にモック/固定シード値を注入し、本番用コードには実際の値を注入します。 依存性注入 を使用します。

_class someclassname {
    private Random _rng;

    constructor(Random rng)

    public String generate(int wordCount)

    private selectRandomStringsFromInternalVocabulary(int wordCount) //words would be a better name here btw
}
_

語彙を依存関係にすることもできます。

また、とにかくこれで修正されるバグがあるかもしれません。私はJavaについては知りませんが、非常に頻繁に呼び出されるメソッド(たとえば、高速ループ)でnew Random()を実行すると、デフォルトのコンストラクターが時間ベースのシードを使用すると、うまくいかない場合があります。

1
Nathan Cooper