6

FsCheck を試してみようと思ったのですが、思ったより難しいことがわかりました。、ジェネレーターなどに関する多くのドキュメントArbがありますが、その知識を適用する方法についてのガイダンスはないようです。または、私はそれを理解していません。

テスト、プロパティ、ジェネレーター、任意、シュリンク、そして私の場合はランダム性 (自動的にランダム データを生成するテストもあれば、生成しないテストもあります) との関係が明確ではないため、理解が難しくなっている可能性があります。私は Haskell のバックグラウンドを持っていないので、それもあまり役に立ちません。

ここで質問があります: ランダムな整数を生成するにはどうすればよいですか?

私のテストシナリオは、乗算の特性で説明できます。たとえば、分配性を考えてみましょう。

static member  ``Multiplication is distributive`` (x: int64) y z =
    let res1 = x * (y + z)
    let res2 = x * y + x * z

    res1 = res2

// run it:
[<Test>]
static member FsCheckAsUnitTest() =
    Check.One({ Config.VerboseThrowOnFailure with MaxTest = 1000 }, ``Multiplication is distributive``)

これを NUnit 統合で実行するとCheck.Verbose、次のようなテスト シーケンスが得られます。

0:
(-1L, -1L, -1L)
1:
(-1L, -1L, 0L)
2:
(-1L, -1L, -1L)
3:
(-1L, -1L, -1L)
4:
(-1L, 0L, -1L)
5:
(1L, 0L, 2L)
6:
(-2L, 0L, -1L)
7:
(-2L, -1L, -1L)
8:
(1L, 1L, -2L)
9:
(-2L, 2L, -2L)

1000回のテストの後、それは乗り越えられませんでした100L. どういうわけか、これが の全範囲に均等に分散された乱数を「自動的に」選択すると想像しint64ました。少なくとも、それがドキュメントの解釈方法でした。

そうでないので、私は実験を開始し、より高い数値を得るために次のようなばかげた解決策を思いつきました:

type Generators = 
    static member arbMyRecord =
        Arb.generate<int64>
        |> Gen.where ((<) 1000L)
        |> Gen.three
        |> Arb.fromGen

しかし、これは信じられないほど遅くなり、明らかに正しいアプローチではありません。私が見逃している簡単な解決策があるに違いないと確信しています。で試しましGen.choose(Int64.MinValue, Int64.MaxValue)たが、これは int のみをサポートし、long はサポートしていません (ただし、int だけでも機能しませんでした)。

最後に、すべてのプリミティブな数値データ型に対して機能するソリューションが必要です。これには、最大値と最小値、ゼロと 1、およびその中にあるものからのランダムな選択が含まれます。

4

1 に答える 1

6

この他の FsCheck の質問で説明されているように、ほとんどのCheck関数の既定の構成にはEndSize = 100. その数を増やすこともできますが、提案どおりに使用することもできますGen.choose

それでも、intジェネレーターは意図的に行儀の良いものになっています。たとえば、オーバーフローが発生する可能性があるため、 Int32.MinValueandは含まれません。Int32.MaxValue

ただし、FsCheck には、範囲全体にわたって一様な分布を与えるジェネレータも付属しています: Arb.Default.DoNotSizeInt16Arb.Default.DoNotSizeUInt64など。

浮動小数点値の場合、Arb.Default.Float32ドキュメントによると、「任意の浮動小数点数、NaN、NegativeInfinity、PositiveInfinity、Maxvalue、MinValue、Epsilon がかなり頻繁に含まれる」を生成する があります。

F# には型クラスがないため (これは Haskell で表現できるものです)、任意の数を「ただ」扱うための統一された API はありません。

また、一般的な単体テスト フレームワークで一般的なテストを実行できるかどうかはわかりませんが、少なくとも xUnit.net では、このトリックを使用して一般的に型指定されたテストを実行できます。


ただし、具体的には、FsCheck.Xunit を使用して、上記のテストを次のように記述できます。

open FsCheck
open FsCheck.Xunit

[<Property>]
let ``Multiplication is distributive`` () =
    Arb.generate<DoNotSize<int64>>
    |> Gen.map (fun (DoNotSize x) -> x)
    |> Gen.three
    |> Arb.fromGen
    |> Prop.forAll <| fun (x, y, z) ->

        let res1 = x * (y + z)
        let res2 = x * y + x * z

        res1 = res2

これは、オーバーフローによって失敗する可能性がありますが、約 1,000,000 のケースを実行した後、まだ失敗していません。

ただし、ジェネレーターは、64 ビット整数の全範囲から値を選択しているように見えます。

> Arb.generate<DoNotSize<int64>> |> Gen.sample 1 10;;
val it : DoNotSize<int64> list =
  [DoNotSize -28197L; DoNotSize -123346460471168L; DoNotSize -28719L;
   DoNotSize -125588489564554L; DoNotSize -29241L;
   DoNotSize 7736726437182770284L; DoNotSize -2382327248148602956L;
   DoNotSize -554678787L; DoNotSize -1317194353L; DoNotSize -29668L]

toのsize引数をバインドしても、「任意に」大きな正と負の値が選択されることに注意してください。Gen.sample1

于 2016-12-02T06:53:49.970 に答える