如何在 FsCheck 中轻松过滤掉受歧视的联合案例?
How to easily filter out a discriminated union case in FsCheck?
考虑一个受歧视的联盟:
type DU = | Foo of string | Bar of int | Baz of decimal * float | Qux of bool
我想用 FsCheck 创建一个包含 DU
个值的列表,但我希望 none 个值属于 Qux
个案例。
这个谓词已经存在:
let isQux = function Qux _ -> true | _ -> false
第一次尝试
我第一次尝试创建一个没有 Qux
大小写的 DU
值列表是这样的:
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
个值?
以下应该有效:
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 无法使用给定的生成器
自行生成列表
而不是 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
考虑一个受歧视的联盟:
type DU = | Foo of string | Bar of int | Baz of decimal * float | Qux of bool
我想用 FsCheck 创建一个包含 DU
个值的列表,但我希望 none 个值属于 Qux
个案例。
这个谓词已经存在:
let isQux = function Qux _ -> true | _ -> false
第一次尝试
我第一次尝试创建一个没有 Qux
大小写的 DU
值列表是这样的:
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
个值?
以下应该有效:
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 无法使用给定的生成器
而不是 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