9

考虑一个有区别的联合:

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

我想DU用 FsCheck 创建一个值列表,但我不希望任何值都是这种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
  • 它并没有真正filter,而只是产生所需的情况(因此仅通过省略进行过滤)。
  • 它不是特别可维护的,因为如果我在 中添加第五个案例DU,我必须记住还要Gen为那个案例添加一个。

有没有更优雅的方法来告诉 FsCheck 过滤掉Qux值?

4

2 回答 2

8

而不是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
于 2015-06-27T06:47:00.057 回答
6

以下应该工作:

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

注意我Gen.listOf最后使用了 - 似乎 FsCheck 无法使用给定的生成器生成自己的列表

于 2015-06-26T09:28:35.017 回答