如何在FsCheck中轻松过滤出歧视的联合案例?

时间:2015-06-26 08:30:48

标签: f# fscheck

考虑一个被歧视的联盟:

type DU = | Foo of string | Bar of int | Baz of decimal * float | Qux of bool

我想使用FsCheck创建DU值列表,但我不希望这些值都是Qux的情况。

此谓词已存在:

let isQux = function Qux _ -> true | _ -> false

首次尝试

我首次尝试在没有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

运行它似乎会产生堆栈溢出,所以我假设场景背后发生的是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

与上述问题相同。

详细解决方案

这是我迄今为止能够提出的最佳解决方案:

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

这有效,但有以下缺点:

  • 好像很多工作
  • 它没有使用已有的isQux功能,所以它似乎巧妙地违反DRY
  • 它并不真正过滤,而只是产生所需的案例(因此只能通过省略过滤)。
  • 它不是特别易于维护,因为如果我向DU添加第五个案例,我必须记住以便为此添加Gen情况下。

是否有更优雅的方式告诉FsCheck过滤掉Qux值?

2 个答案:

答案 0 :(得分:7)

而不是试图使用已注册的实例类型的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

答案 1 :(得分:4)

The below should work:

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

Note I used Gen.listOf at the end - seems like FsCheck fails to generate itself a list with the given generator