4

FsCheck を使用して複数の引数の生成を実装するにはどうすればよいですか?

複数の引数の生成をサポートするために、次を実装しました。

// Setup
let pieces =    Arb.generate<Piece> |> Gen.filter (isKing >> not)
                                    |> Arb.fromGen  

let positionsList = Arb.generate<Space list> |> Arb.fromGen

次に、これらの引数を使用して、特定のチェッカーの移動オプションの生成を担当する関数の動作をテストしました。

// Test
Prop.forAll pieces <| fun piece ->
    Prop.forAll positionsList <| fun positionsItem ->

        positionsItem |> optionsFor piece 
                      |> List.length <= 2

生成された複数の引数の型を管理する場合、Prop.forAll 式のネストは適切な手法ですか?

テスト対象の関数に複数の引数を生成する別の方法はありますか?

関数全体は次のとおりです。

open FsCheck
open FsCheck.Xunit

[<Property(QuietOnSuccess = true)>]
let ``options for soldier can never exceed 2`` () =

    // Setup
    let pieces =    Arb.generate<Piece> |> Gen.filter (isKing >> not)
                                        |> Arb.fromGen  

    let positionsList = Arb.generate<Space list> |> Arb.fromGen

    // Test
    Prop.forAll pieces <| fun piece ->
        Prop.forAll positionsList <| fun positionsItem ->

            positionsItem |> optionsFor piece 
                          |> List.length <= 2

アップデート

マークの答えから導き出された私の質問に対する解決策は次のとおりです。

[<Property(QuietOnSuccess = true, MaxTest=100)>]
let ``options for soldier can never exceed 2`` () =

    // Setup
    let pieceGen =     Arb.generate<Piece> |> Gen.filter (isKing >> not)
    let positionsGen = Arb.generate<Space list>

    // Test
    (pieceGen , positionsGen) ||> Gen.map2 (fun x y -> x,y)
                               |> Arb.fromGen
                               |> Prop.forAll <| fun (piece , positions) -> 
                                                   positions |> optionsFor piece 
                                                             |> List.length <= 2
4

1 に答える 1

8

一般的な観察として、Arbitrary値は構成するのが難しいのに対し、Gen値は簡単です。そのため、私は FsCheck の構築ブロックをGen<'a>ではなく で定義する傾向がありますArbitrary<'a>

値を使用すると、、、などGenを使用して複数の引数を構成したり、計算式を使用したりできます。Gen.map2Gen.map3gen

生成ブロック

OP の例では、piecesandpositionsListを as として定義する代わりにArbitrary、それらをGen値として定義します。

let genPieces = Arb.generate<Piece> |> Gen.filter (isKing >> not)

let genPositionsList = Arb.generate<Space list>

Gen<Piece>これらは、それぞれタイプおよびの「構成要素」ですGen<Space list>

genPiecesなどではなく、名前を付けたことに注意してくださいpieces。これにより、後で名前が衝突するのを防ぐことができます (以下を参照)。(また、単一の値しか生成しないため、複数形の の使用についてはわかりませんが、ドメイン全体がわからないため、そのままにしておくことにしました。)piecesgenPiecesPiece

それらの 1 つだけが必要な場合は、それをArbitraryusingに変換できますArb.fromGen

それらを構成する必要がある場合は、以下に示すように、マップ関数または計算式のいずれかを使用できます。これGenによりタプルの が得られ、 を使用Arb.fromGenしてそれを に変換できますArbitrary

map2 を使用して作成する

構成して引数リストにする必要がある場合はpiecespositionsList次を使用できますGen.map2

Gen.map2 (fun x y -> x, y) genPieces genPositionList
|> Arb.fromGen
|> Prop.forAll <| fun (pieces, positionList) -> 
    // test goes here...

Gen.map2 (fun x y -> x, y)値の 2 要素タプル (ペア(pieces, positionList)) を返します。これは、無名関数で分解できます。

この例では、なぜgenPiecesgenPositionListが値のより適切な名前であるかを明確にする必要があります。これらは、「そのままの」名前と、テスト本体に渡される生成された値Genを使用する余地を残しています。piecespositionList

演算式で合成

より複雑な組み合わせで私が時々好む別の方法は、gen計算式を使用することです。

上記の例は、次のように書くこともできます。

gen {
    let! pieces = genPieces
    let! positionList = genPositionList
    return pieces, positionList }
|> Arb.fromGen
|> Prop.forAll <| fun (pieces, positionList) -> 
    // test goes here...

最初のgen式もペアを返すので、 を使用した合成と同等Gen.map2です。

最も読みやすいと思われるオプションを使用できます。

自明でないGen組み合わせの例は、私の記事Roman numbers via property-based TDDで見ることができます。

于 2016-08-09T02:46:39.727 に答える