如何使用FsCheck实现多个参数生成?
我实现了以下内容以支持多个参数生成:
// Setup
let pieces = Arb.generate<Piece> |> Gen.filter (isKing >> not)
|> Arb.fromGen
let positionsList = Arb.generate<Space list> |> Arb.fromGen
Run Code Online (Sandbox Code Playgroud)
然后我使用这些参数来测试一个函数的行为,该函数负责为给定的检查器生成移动选项:
// Test
Prop.forAll pieces <| fun piece ->
Prop.forAll positionsList <| fun positionsItem ->
positionsItem |> optionsFor piece
|> List.length <= 2
Run Code Online (Sandbox Code Playgroud)
在管理多个生成的参数类型时,是否将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
Run Code Online (Sandbox Code Playgroud)
UPDATE
以下是我从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
Run Code Online (Sandbox Code Playgroud)
作为一般观察,Arbitrary值很难组合,而Gen值很容易.出于这个原因,我倾向于用Gen<'a>而不是来定义我的FsCheck构建块Arbitrary<'a>.
有了Gen值,则可以使用组合多个参数Gen.map2,Gen.map3,等等,或者您也可以使用gen计算表达式.
Gen积木
在OP例如,代替限定pieces和positionsList如Arbitrary,将它们定义为Gen值:
let genPieces = Arb.generate<Piece> |> Gen.filter (isKing >> not)
let genPositionsList = Arb.generate<Space list>
Run Code Online (Sandbox Code Playgroud)
这些类型的"积木" Gen<Piece>,并Gen<Space list>分别.
请注意,我将它们命名为genPieces而不是简单pieces,依此类推.这可以防止名称冲突(见下文).(另外,我不知道关于使用复数的小号中pieces,由于genPieces仅生成一个Piece值,但我因为我不知道你的整个领域,我决定离开,由于是).
如果您只需要其中一个,则可以将其转换为Arbitrary使用Arb.fromGen.
如果需要编写它们,可以使用其中一个映射函数或计算表达式,如下所示.这将为您提供一个Gen元组,然后您可以使用Arb.fromGen它将其转换为Arbitrary.
使用map2撰写
如果您需要撰写pieces并positionsList进入参数列表,您可以使用Gen.map2:
Gen.map2 (fun x y -> x, y) genPieces genPositionList
|> Arb.fromGen
|> Prop.forAll <| fun (pieces, positionList) ->
// test goes here...
Run Code Online (Sandbox Code Playgroud)
Gen.map2 (fun x y -> x, y)返回一个两元素元组(一对)值,您可以(pieces, positionList)在匿名函数中进行解构.
这个例子还应该清楚说明为什么genPieces和genPositionList更好的名称Gen值:它们留有空间使用'裸'名称pieces和positionList传递给测试体的生成值.
使用计算表达式撰写
我有时更喜欢更复杂组合的另一种选择是使用gen计算表达式.
上面的例子也可以这样写:
gen {
let! pieces = genPieces
let! positionList = genPositionList
return pieces, positionList }
|> Arb.fromGen
|> Prop.forAll <| fun (pieces, positionList) ->
// test goes here...
Run Code Online (Sandbox Code Playgroud)
初始gen表达式也返回一对,因此它等同于组合Gen.map2.
您可以使用最易读的选项.
您可以通过基于属性的TDDGen在我的文章罗马数字中看到更多非平凡组合的例子.