如何使用 FsCheck 实现多参数生成?
How do I implement multiple argument generation using FsCheck?
如何使用 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
更新
这是从 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
一般来说,Arbitrary
值很难组合,而 Gen
值很容易组合。出于这个原因,我倾向于根据 Gen<'a>
而不是 Arbitrary<'a>
.
来定义我的 FsCheck 构建块
对于 Gen
值,您可以使用 Gen.map2
、Gen.map3
等组合多个参数,或者您可以使用 gen
计算表达式。
创世积木
在 OP 示例中,不是将 pieces
和 positionsList
定义为 Arbitrary
,而是将它们定义为 Gen
值:
let genPieces = Arb.generate<Piece> |> Gen.filter (isKing >> not)
let genPositionsList = Arb.generate<Space list>
这些 'building blocks' 类型分别为 Gen<Piece>
和 Gen<Space list>
。
请注意,我将它们命名为 genPieces
而不是简单的 pieces
,依此类推。这可以防止以后发生名称冲突(见下文)。 (此外,我不确定在 pieces
中使用复数 s,因为 genPieces
只生成一个 Piece
值,但是因为我不知道您的整个域,所以我决定保持原样。)
如果您只需要其中一个,可以使用 Arb.fromGen
.
将其转换为 Arbitrary
如果您需要组合它们,您可以使用地图函数或计算表达式之一,如下所示。这将为您提供 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...
Gen.map2 (fun x y -> x, y)
returns 值的双元素元组(对),您可以在匿名中将其解构为 (pieces, positionList)
功能。
这个例子也应该清楚地说明为什么 genPieces
和 genPositionList
是 Gen
值更好的名字:它们为使用 'naked' 名字留出了空间 pieces
和 positionList
用于传递给测试主体的生成值。
使用计算表达式组合
对于更复杂的组合,我有时更喜欢的另一种选择是使用 gen
计算表达式。
上面的例子也可以这样写:
gen {
let! pieces = genPieces
let! positionList = genPositionList
return pieces, positionList }
|> Arb.fromGen
|> Prop.forAll <| fun (pieces, positionList) ->
// test goes here...
初始的gen
表达式也是returns一对,所以相当于和Gen.map2
组合。
您可以使用您认为最易读的选项。
您可以在我的文章 Roman numerals via property-based TDD 中看到更多非平凡 Gen
组合的示例。
如何使用 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
更新
这是从 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
一般来说,Arbitrary
值很难组合,而 Gen
值很容易组合。出于这个原因,我倾向于根据 Gen<'a>
而不是 Arbitrary<'a>
.
对于 Gen
值,您可以使用 Gen.map2
、Gen.map3
等组合多个参数,或者您可以使用 gen
计算表达式。
创世积木
在 OP 示例中,不是将 pieces
和 positionsList
定义为 Arbitrary
,而是将它们定义为 Gen
值:
let genPieces = Arb.generate<Piece> |> Gen.filter (isKing >> not)
let genPositionsList = Arb.generate<Space list>
这些 'building blocks' 类型分别为 Gen<Piece>
和 Gen<Space list>
。
请注意,我将它们命名为 genPieces
而不是简单的 pieces
,依此类推。这可以防止以后发生名称冲突(见下文)。 (此外,我不确定在 pieces
中使用复数 s,因为 genPieces
只生成一个 Piece
值,但是因为我不知道您的整个域,所以我决定保持原样。)
如果您只需要其中一个,可以使用 Arb.fromGen
.
Arbitrary
如果您需要组合它们,您可以使用地图函数或计算表达式之一,如下所示。这将为您提供 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...
Gen.map2 (fun x y -> x, y)
returns 值的双元素元组(对),您可以在匿名中将其解构为 (pieces, positionList)
功能。
这个例子也应该清楚地说明为什么 genPieces
和 genPositionList
是 Gen
值更好的名字:它们为使用 'naked' 名字留出了空间 pieces
和 positionList
用于传递给测试主体的生成值。
使用计算表达式组合
对于更复杂的组合,我有时更喜欢的另一种选择是使用 gen
计算表达式。
上面的例子也可以这样写:
gen {
let! pieces = genPieces
let! positionList = genPositionList
return pieces, positionList }
|> Arb.fromGen
|> Prop.forAll <| fun (pieces, positionList) ->
// test goes here...
初始的gen
表达式也是returns一对,所以相当于和Gen.map2
组合。
您可以使用您认为最易读的选项。
您可以在我的文章 Roman numerals via property-based TDD 中看到更多非平凡 Gen
组合的示例。