考虑一个被歧视的联盟:
type DU = | Foo of string | Bar of int | Baz of decimal * float | Qux of bool
Run Code Online (Sandbox Code Playgroud)
我想DU用FsCheck 创建一个值列表,但我不希望这些值都是Qux这样的.
这个谓词已经存在:
let isQux = function Qux _ -> true | _ -> false
Run Code Online (Sandbox Code Playgroud)
第一次尝试
我第一次尝试创建一个DU没有Qux大小写的值列表是这样的:
type DoesNotWork =
static member DU () = Arb.from<DU> |> Arb.filter (not << isQux)
[<Property(MaxTest = 10 , Arbitrary = [| typeof<DoesNotWork> |])>]
let repro (dus : DU list) =
printfn "%-5b : %O" (dus |> List.exists isQux |> not) dus
Run Code Online (Sandbox Code Playgroud)
运行这似乎会产生堆栈溢出,所以我假设在场景后面发生的是Arb.from<DU>调用DoesNotWork.DU.
第二次尝试
然后我尝试了这个:
type DoesNotWorkEither =
static member DU () =
Arb.generate<DU>
|> Gen.suchThat (not << isQux)
|> Arb.fromGen
[<Property(MaxTest = 10 , Arbitrary = [| typeof<DoesNotWorkEither> |])>]
let repro (dus : DU list) =
printfn "%-5b : %O" (dus |> List.exists isQux |> not) dus
Run Code Online (Sandbox Code Playgroud)
与上述问题相同.
详细的解决方案
这是迄今为止我能够提出的最佳解决方案:
type WithoutQux =
static member DU () =
[
Arb.generate<string> |> Gen.map Foo
Arb.generate<int> |> Gen.map Bar
Arb.generate<decimal * float> |> Gen.map Baz
]
|> Gen.oneof
|> Arb.fromGen
[<Property(MaxTest = 10 , Arbitrary = [| typeof<WithoutQux> |])>]
let repro (dus : DU list) =
printfn "%-5b : %O" (dus |> List.exists isQux |> not) dus
Run Code Online (Sandbox Code Playgroud)
这有效,但有以下缺点:
isQux功能,因此它似乎巧妙地违反DRYDU,我将不得不记得也Gen为该案例添加一个.是否有更优雅的方式告诉FsCheck过滤掉Qux值?
而不是Arb.generate尝试使用已注册实例的类型(即您尝试定义的实例,这会导致无限循环),Arb.Default.Derive()而是直接使用基于反射的生成器.
https://github.com/fscheck/FsCheck/blob/master/src/FsCheck/Arbitrary.fs#L788-788
这是我们应该能够在FsCheck中开箱即用的常见错误:https://github.com/fscheck/FsCheck/issues/109
OP中的特殊问题可以像这样解决:
type WithoutQux =
static member DU () = Arb.Default.Derive () |> Arb.filter (not << isQux)
[<Property(MaxTest = 10 , Arbitrary = [| typeof<WithoutQux> |])>]
let repro (dus : DU list) =
printfn "%-5b : %O" (dus |> List.exists isQux |> not) dus
Run Code Online (Sandbox Code Playgroud)
下面应该工作:
type DU = | Foo of string | Bar of int | Baz of decimal * float | Qux of bool
let isQux = function Qux _ -> true | _ -> false
let g = Arb.generate<DU> |> Gen.suchThat (not << isQux) |> Gen.listOf
type DoesWork =
static member DU () = Arb.fromGen g
[<Property(MaxTest = 10 , Arbitrary = [| typeof<DoesWork> |])>]
let repro (dus : DU list) =
printfn "%-5b : %O" (dus |> List.exists isQux |> not) dus
Run Code Online (Sandbox Code Playgroud)
注意我Gen.listOf在最后使用 - 似乎 FsCheck 无法使用给定的生成器生成自己的列表