如何使用FsCheck实现多参数生成? [英] How do I implement multiple argument generation using 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.map2
,Gen.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示例中,不要将pieces
和positionsList
定义为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撰写
如果需要将pieces
和positionsList
组成一个参数列表,则可以使用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.
此示例还应明确说明为什么genPieces
和genPositionList
是Gen
值的更好名称:它们留出空间将裸"名称pieces
和positionList
用于传递的生成值进入测试机构.
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.
您可以在我的文章 查看全文