【问题标题】:How do I implement multiple argument generation using FsCheck?如何使用 FsCheck 实现多参数生成?
【发布时间】:2016-08-09 17:58:12
【问题描述】:

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

【问题讨论】:

    标签: f# fscheck fsunit


    【解决方案1】:

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

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

    代构建模块

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

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

    这些分别是 Gen&lt;Piece&gt;Gen&lt;Space list&gt; 类型的“构建块”。

    请注意,我将它们命名为 genPieces 而不是简单的 pieces,等等。这可以防止以后出现名称冲突(见下文)。 (另外,我不确定在pieces 中使用复数s,因为genPieces 只生成一个Piece 值,但我因为我不知道你的全部域,我决定保持原样。)

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

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

    这个例子还应该清楚为什么genPiecesgenPositionListGen 值的更好名称:它们为传递的生成值留出了使用“裸”名称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 组合示例。

    【讨论】:

    猜你喜欢
    • 2012-10-05
    • 1970-01-01
    • 2016-07-19
    • 1970-01-01
    • 2020-01-14
    • 2018-06-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多