如何使用FsCheck实现多参数生成? [英] How do I implement multiple argument generation using FsCheck?

查看:98
本文介绍了如何使用FsCheck实现多参数生成?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

I implemented the following to support multiple argument generation:

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

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

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

I then used these arguments to test the behavior of a function that's responsible for generating move options for a given checker:

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

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

管理多个生成的参数类型时,嵌套Prop.forAll表达式是正确的技术吗?

Is nesting Prop.forAll expressions the right technique when managing multiple generated argument types?

是否有另一种方法可以为被测函数生成多个参数?

Is there an alternative method for generating multiple arguments for a function under test?

这是整个功能:

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

更新

这是从马克的答案得到的我的问题的解决方案:

Here's the solution to my question derived from Mark's answer:

[<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

推荐答案

作为一般观察,Arbitrary值很难编写,而Gen值很容易.因此,我倾向于用Gen<'a>而不是Arbitrary<'a>定义我的FsCheck构建块.

As a general observation, Arbitrary values are difficult to compose, whereas Gen values are easy. For that reason, I tend to define my FsCheck building blocks in terms of Gen<'a> instead of Arbitrary<'a>.

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

With Gen values, you can compose multiple arguments using Gen.map2, Gen.map3, etcetera, or you can use the gen computation expression.

Gen构件块

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

In the OP example, instead of defining pieces and positionsList as Arbitrary, define them as Gen values:

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

let genPositionsList = Arb.generate<Space list>

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

These are 'building blocks' of the types Gen<Piece> and Gen<Space list>, respectively.

请注意,我将它们命名为genPieces,而不是简单的pieces,依此类推.这样可以防止以后发生名称冲突(请参阅下文). (另外,我不确定在pieces中使用复数 s 的原因,因为genPieces仅生成单个Piece值,但由于我不知道您的全部域,我决定将其保留.)

Notice that I named them genPieces instead of simply pieces, and so on. This prevents name collisions later on (see below). (Also, I'm not sure about the use of the plural s in pieces, because genPieces only generates a single Piece value, but I since I don't know your entire domain, I decided to leave that as is.)

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

If you need only one of them, you can convert it into an Arbitrary using Arb.fromGen.

如果需要组合它们,可以使用以下任何一种map函数或计算表达式,如下所示.这将为您提供元组的Gen,然后您可以使用Arb.fromGen将其转换为Arbitrary.

If you need to compose them, you can use either one of the map functions, or computation expressions, as shown below. This will give you a Gen of tuples, and you can then use Arb.fromGen to convert that into an Arbitrary.

使用map2撰写

如果需要将piecespositionsList组成一个参数列表,则可以使用Gen.map2:

If you need to compose pieces and positionsList into an argument list, you can use 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).

Gen.map2 (fun x y -> x, y) returns a two-element tuple (a pair) of values, which you can destructure into (pieces, positionList) in the anonymous function.

此示例还应明确说明为什么genPiecesgenPositionListGen值的更好名称:它们留出空间将裸"名称piecespositionList用于传递的生成值进入测试机构.

This example should also make it clear why genPieces and genPositionList are better names for the Gen values: they leave room to use the 'naked' names pieces and positionList for the generated values passed to the test body.

使用计算表达式编写

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

Another alternative that I sometimes prefer for more complex combinations is to use the gen computation expression.

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

The above example could also be written like this:

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

初始的gen表达式也返回一对,因此等效于Gen.map2的合成.

The initial gen expression also returns a pair, so it's equivalent to composition with Gen.map2.

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

You can use the option you find most readable.

您可以在我的文章 查看全文

登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆