如何使用FsCheck实现多个参数生成?

Sco*_*rod 3 f# fscheck fsunit

如何使用FsCheck实现多个参数生成?

我实现了以下内容以支持多个参数生成:

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

let positionsList = Arb.generate<Space list> |> Arb.fromGen
Run Code Online (Sandbox Code Playgroud)

然后我使用这些参数来测试一个函数的行为,该函数负责为给定的检查器生成移动选项:

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

        positionsItem |> optionsFor piece 
                      |> List.length <= 2
Run Code Online (Sandbox Code Playgroud)

在管理多个生成的参数类型时,是否将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
Run Code Online (Sandbox Code Playgroud)

UPDATE

以下是我从Mark的回答中得出的问题的解决方案:

[<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
Run Code Online (Sandbox Code Playgroud)

Mar*_*ann 6

作为一般观察,Arbitrary值很难组合,而Gen值很容易.出于这个原因,我倾向于用Gen<'a>而不是来定义我的FsCheck构建块Arbitrary<'a>.

有了Gen值,则可以使用组合多个参数Gen.map2,Gen.map3,等等,或者您也可以使用gen计算表达式.

Gen积木

在OP例如,代替限定piecespositionsListArbitrary,将它们定义为Gen值:

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

let genPositionsList = Arb.generate<Space list>
Run Code Online (Sandbox Code Playgroud)

这些类型的"积木" Gen<Piece>,并Gen<Space list>分别.

请注意,我将它们命名为genPieces而不是简单pieces,依此类推.这可以防止名称冲突(见下文).(另外,我不知道关于使用复数的小号pieces,由于genPieces仅生成一个Piece值,但我因为我不知道你的整个领域,我决定离开,由于是).

如果您只需要其中一个,则可以将其转换为Arbitrary使用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...
Run Code Online (Sandbox Code Playgroud)

Gen.map2 (fun x y -> x, y)返回一个两元素元组(一)值,您可以(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...
Run Code Online (Sandbox Code Playgroud)

初始gen表达式也返回一对,因此它等同于组合Gen.map2.

您可以使用最易读的选项.

您可以通过基于属性的TDDGen在我的文章罗马数字中看到更多非平凡组合的例子.