我定义了一个带有一些自定义生成器的类型,以使FsCheck生成几种类型的自定义实例。但对于其中一种复杂类型,我想首先使用默认的FsCheck生成,然后调整结果。这是一个(简化的)代码:
type CustomGenerators =
static member FirstCustomType() = /* function that returns FirstCustomType */
static member SecondCustomType() =
Arb.generate<SecondCustomType>
|> Gen.map (fun x -> /* adjust some data in the generated instance */)
|> Arb.fromGen
问题是,当SecondCustomType()静态方法调用Arb.generate时,它会立即调用SecondCustomType(),从而导致无限递归。我知道Arb.generate必须尊重自定义生成器,所以这就是它调用静态SecondCustomType()的原因,但我需要调用SecondCustomType的默认(非自定义)Arb.generate实现。我不能从不同类型调用实现,因为我的自定义生成器使用自定义生成器用于FirstCustomType,因此默认的SecondCustomType实现必须知道CustomGenerators类型中定义的所有自定义生成器。这是一个糟糕的循环,我还没有找到一个干净的解决方案(只有解决方法)。
答案 0 :(得分:4)
所有“默认”(即开箱即用)生成器都在FsCheck.Arb.Default
类上。根据{{1}}实际上的内容,您可以使用该类中的某些方法,例如Bool
或String
。
如果您的类型是正确的代数F#类型(即联合,记录或元组),您可以利用自动派生的生成器来处理由Default.Derive
表示的类型。
SecondCustomType
话虽如此,我同意Mark上面的评论:使用这些静态方法 - shim-for-type-class生成器总是有点尴尬。就像Mark一样,我更喜欢让FsCheck提供开箱即用的功能,然后使用常规功能组合所需的输入。我给你举个例子。
考虑这种类型,大概不能由FsCheck开箱即用:
type CustomGenerators =
static member SecondCustomType() =
Arb.Default.Derive<SecondCustomType>()
|> Arb.toGen
|> Gen.map (fun x -> (* adjust some data in the generated instance *) )
|> Arb.fromGen
这是使用static-shim-for-type-class生成器的尴尬方式:
type SomeAwkwardType( name: string, id: int, flag: bool ) =
member this.Name = name
member this.Id = id
member this.Flag = flag
这是生成输入的更简单(在我看来)的方式:
type AwkwardTypeGenerator() =
static member Gen() =
gen {
let! name = Arb.generate<string>
let! id = Arb.generate<int>
let! flag = Arb.generate<bool>
return SomeAwkwardType( name, id, flag )
}
module Tests =
let [Property] ``Test using the awkward generator`` (input: SomeAwkwardType) =
someFn input = 42
这不仅更短更清洁,而且还具有一年后不用拔头发的优势,不得不查看实现生成器的静态类的代码库。