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

时间:2016-08-08 22:58:57

标签: f# fscheck fsunit

如何使用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

更新

以下是我从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

1 个答案:

答案 0 :(得分:6)

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

使用Gen值,您可以使用Gen.map2Gen.map3等编写多个参数,也可以使用gen计算表达式。

Gen building blocks

在OP示例中,不要将piecespositionsList定义为Arbitrary,而是将其定义为Gen值:

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

let genPositionsList = Arb.generate<Space list>

这些是&#39;构建块&#39;分别为Gen<Piece>Gen<Space list>类型。

请注意,我将它们命名为genPieces,而不仅仅是pieces,依此类推。这可以防止名称冲突(见下文)。 (另外,我不确定在pieces中使用复数 s ,因为genPieces只生成一个Piece值,但我因为我不知道你的整个域名,所以我决定保留原样。)

如果您只需要其中一个,则可以使用Arbitrary将其转换为Arb.fromGen

如果需要编写它们,可以使用其中一个map函数或计算表达式,如下所示。这将为您提供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)返回一个两元素元组( pair )的值,您可以在匿名函数中将其解构为(pieces, positionList)

此示例还应明确为什么genPiecesgenPositionListGen值的更好名称:它们留有空间使用“裸”&#39;名称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的合成相当。

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

您可以在我的文章Roman numerals via property-based TDD中看到更多关于非平凡Gen组合的示例。