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值很容易。Gen<'a>出于这个原因,我倾向于用代替来定义我的 FsCheck 构建块Arbitrary<'a>

使用值,您可以使用、等Gen组合多个参数,也可以使用计算表达式。Gen.map2Gen.map3gen

世代积木

在 OP 示例中,不是将piecesand定义positionsList为,而是Arbitrary将它们定义为Gen值:

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

let genPositionsList = Arb.generate<Space list>

这些分别是 和 类型的“构建块Gen<Piece>Gen<Space list>

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

如果您只需要其中一个,则可以将其转换为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)返回一个包含两个元素的元组(一)值,您可以(pieces, positionList)在匿名函数中对其进行解构。

这个例子还应该清楚地说明为什么genPieces并且genPositionList是更好的Gen值名称:它们为使用“裸”名称piecespositionList传递给测试主体的生成值留出了空间。

使用计算表达式编写

对于更复杂的组合,我有时更喜欢的另一种选择是使用gen计算表达式。

上面的例子也可以这样写:

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

初始gen表达式也返回一个对,所以它等同于组合 with Gen.map2

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

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

于 2016-08-09T02:46:39.727 回答