FsCheck DataGen 为空
FsCheck DataGen is null
我正在尝试通过一个 FsCheck 示例来处理具有可区分联合的类型,以便为我们的大型项目建立最佳实践。现在我从生成器中得到 null,我不确定为什么。在以下代码中,DataGen.containerGenerator 为空。
namespace Container
open System
open Xunit
open FsCheck
module ContainerLibrary =
type [<Measure>] oz
type Container =
| Cup of Common
| Bowl of Common
and Common =
{ Volume :decimal<oz>
Weight :decimal}
module DataGen =
type Generators =
static member arbVolume =
FsCheck.Gen.choose (1, 16)
|> FsCheck.Gen.map(fun x -> (decimal x / 8.0M) * 1.0M<ContainerLibrary.oz>)
|> FsCheck.Arb.fromGen
FsCheck.Arb.register<Generators>() |> ignore
let bowlGenerator =
FsCheck.Gen.map2 (fun a b -> ContainerLibrary.Bowl( { Volume = a
Weight = b}))
(Generators.arbVolume.Generator)
(FsCheck.Arb.generate<decimal>)
let cupGenerator =
FsCheck.Gen.map2 (fun a b -> ContainerLibrary.Cup( { Volume = a
Weight = b}))
(Generators.arbVolume.Generator)
(FsCheck.Arb.generate<decimal>)
let containerGenerator =
Gen.oneof [bowlGenerator; cupGenerator]
module Tests =
[<Fact;>]
let ``01 : Containers must be no more than 20 oz`` () =
//Is this the best way to get one of something?
let c = FsCheck.Gen.sample 0 1 DataGen.containerGenerator |> Seq.head
Assert.NotNull (c)
当我 运行 它似乎不是空的,即使我得到更多的值。您使用的是哪个版本的 FsCheck?
[<Fact;>]
let ``01 : Containers must be no more than 20 oz`` () =
//Is this the best way to get one of something?
Gen.sample 0 100 DataGen.containerGenerator |> Seq.iter(fun c -> printf "%A" c; Assert.NotNull (c))
无论如何,关于您正在做的事情,有几点需要注意。
- FsCheck 使用反射来注册生成器;无法通过反射看到测量类型参数的类型。所以 Arb.register 实际上会覆盖所有小数的小数生成器。
- FsCheck 不知何故。你使用的资格混淆了 intellisense 没完没了。
- Gen.sample 是测试生成器的合理方法,但我主要在交互式设置中使用它;如果您经历过设置测试的麻烦,我倾向于使用 FsCheck 的内置测试用例观察功能。请参阅此处 "Observing test case distribution":https://fsharp.github.io/FsCheck/Properties.html
- 像您一样在模块初始化中使用 Arb.register 有点脆弱,这取决于 F# 中注册生成器的模块初始化规则。如果您使用 Xunit,最好使用内置集成来减少这方面不可避免的挫败感。
考虑到以下几点,我稍微重写了您的示例:
module DataGen =
open ContainerLibrary
//can't really register this one because of the measure, would override all decimal generatos
let volumeGenerator =
Gen.choose (1, 16)
|> Gen.map(fun x -> (decimal x / 8.0M) * 1.0M<ContainerLibrary.oz>)
let commonGenerator =
Gen.map2 (fun a b -> { Volume = a
Weight = b})
(volumeGenerator)
(Arb.generate<decimal>)
//in case you like applicative style, otherwise completely equivalent
let commonGeneratorAlternative =
(fun a b -> { Volume = a; Weight = b}) <!> volumeGenerator <*> Arb.generate<decimal>
let bowlGenerator = Gen.map Bowl commonGenerator
let cupGenerator = Gen.map Cup commonGenerator
let containerGenerator =
Gen.oneof [bowlGenerator; cupGenerator]
type Generators =
static member Container() = containerGenerator |> Arb.fromGen
module Tests =
open FsCheck.Xunit
open ContainerLibrary
//use PropertyAttribute from FsCheck.Xunit
//use the defined container generator - can also move this to module level
//other ways to parametrize
[<Property(Arbitrary=[|typeof<DataGen.Generators>|])>]
//thanks to PropertyAttribute can now just take container as argument
let ``01 : Containers must be no more than 20 oz`` (container:Container) =
match container with
| Cup common
| Bowl common -> common.Volume <= 20.0M<oz>
|> Prop.collect container //see the generated values in the output
输出如下:
好的,通过了 100 次测试。
1% 杯 {体积 = 2.0M;
权重 = -0.0000221360928858744815609M;}。
1% Cup {体积 = 1.8750M;
权重 = 922337.20325598085121M;}.
等等
我正在尝试通过一个 FsCheck 示例来处理具有可区分联合的类型,以便为我们的大型项目建立最佳实践。现在我从生成器中得到 null,我不确定为什么。在以下代码中,DataGen.containerGenerator 为空。
namespace Container
open System
open Xunit
open FsCheck
module ContainerLibrary =
type [<Measure>] oz
type Container =
| Cup of Common
| Bowl of Common
and Common =
{ Volume :decimal<oz>
Weight :decimal}
module DataGen =
type Generators =
static member arbVolume =
FsCheck.Gen.choose (1, 16)
|> FsCheck.Gen.map(fun x -> (decimal x / 8.0M) * 1.0M<ContainerLibrary.oz>)
|> FsCheck.Arb.fromGen
FsCheck.Arb.register<Generators>() |> ignore
let bowlGenerator =
FsCheck.Gen.map2 (fun a b -> ContainerLibrary.Bowl( { Volume = a
Weight = b}))
(Generators.arbVolume.Generator)
(FsCheck.Arb.generate<decimal>)
let cupGenerator =
FsCheck.Gen.map2 (fun a b -> ContainerLibrary.Cup( { Volume = a
Weight = b}))
(Generators.arbVolume.Generator)
(FsCheck.Arb.generate<decimal>)
let containerGenerator =
Gen.oneof [bowlGenerator; cupGenerator]
module Tests =
[<Fact;>]
let ``01 : Containers must be no more than 20 oz`` () =
//Is this the best way to get one of something?
let c = FsCheck.Gen.sample 0 1 DataGen.containerGenerator |> Seq.head
Assert.NotNull (c)
当我 运行 它似乎不是空的,即使我得到更多的值。您使用的是哪个版本的 FsCheck?
[<Fact;>]
let ``01 : Containers must be no more than 20 oz`` () =
//Is this the best way to get one of something?
Gen.sample 0 100 DataGen.containerGenerator |> Seq.iter(fun c -> printf "%A" c; Assert.NotNull (c))
无论如何,关于您正在做的事情,有几点需要注意。
- FsCheck 使用反射来注册生成器;无法通过反射看到测量类型参数的类型。所以 Arb.register 实际上会覆盖所有小数的小数生成器。
- FsCheck 不知何故。你使用的资格混淆了 intellisense 没完没了。
- Gen.sample 是测试生成器的合理方法,但我主要在交互式设置中使用它;如果您经历过设置测试的麻烦,我倾向于使用 FsCheck 的内置测试用例观察功能。请参阅此处 "Observing test case distribution":https://fsharp.github.io/FsCheck/Properties.html
- 像您一样在模块初始化中使用 Arb.register 有点脆弱,这取决于 F# 中注册生成器的模块初始化规则。如果您使用 Xunit,最好使用内置集成来减少这方面不可避免的挫败感。
考虑到以下几点,我稍微重写了您的示例:
module DataGen =
open ContainerLibrary
//can't really register this one because of the measure, would override all decimal generatos
let volumeGenerator =
Gen.choose (1, 16)
|> Gen.map(fun x -> (decimal x / 8.0M) * 1.0M<ContainerLibrary.oz>)
let commonGenerator =
Gen.map2 (fun a b -> { Volume = a
Weight = b})
(volumeGenerator)
(Arb.generate<decimal>)
//in case you like applicative style, otherwise completely equivalent
let commonGeneratorAlternative =
(fun a b -> { Volume = a; Weight = b}) <!> volumeGenerator <*> Arb.generate<decimal>
let bowlGenerator = Gen.map Bowl commonGenerator
let cupGenerator = Gen.map Cup commonGenerator
let containerGenerator =
Gen.oneof [bowlGenerator; cupGenerator]
type Generators =
static member Container() = containerGenerator |> Arb.fromGen
module Tests =
open FsCheck.Xunit
open ContainerLibrary
//use PropertyAttribute from FsCheck.Xunit
//use the defined container generator - can also move this to module level
//other ways to parametrize
[<Property(Arbitrary=[|typeof<DataGen.Generators>|])>]
//thanks to PropertyAttribute can now just take container as argument
let ``01 : Containers must be no more than 20 oz`` (container:Container) =
match container with
| Cup common
| Bowl common -> common.Volume <= 20.0M<oz>
|> Prop.collect container //see the generated values in the output
输出如下:
好的,通过了 100 次测试。
1% 杯 {体积 = 2.0M; 权重 = -0.0000221360928858744815609M;}。 1% Cup {体积 = 1.8750M; 权重 = 922337.20325598085121M;}. 等等