从相同类型的自定义生成器调用默认 FsCheck 生成器

Calling default FsCheck generator from a custom generator of the same type

我定义了一个带有几个自定义生成器的类型,使 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 类型中定义的所有自定义生成器。这是一个糟糕的循环,我还没有找到一个干净的解决方案(仅解决方法)。

所有 "default"(即开箱即用)生成器都在 FsCheck.Arb.Default class. Depending on what your SecondCustomType actually is, you can use some of the methods on that class, such as Bool or String

如果您的类型是适当的代数 F# 类型(即联合、记录或元组),您可以利用由 Default.Derive.[=21= 表示的此类类型的自动派生生成器]

type CustomGenerators =
    static member SecondCustomType() =
        Arb.Default.Derive<SecondCustomType>()
        |> Arb.toGen
        |> Gen.map (fun x -> (* adjust some data in the generated instance *) )
        |> Arb.fromGen

话虽如此,我同意 Mark 上面的评论:使用这些 static-method-shim-for-type-class 生成器总是有点尴尬。就像 Mark 一样,我更喜欢让 FsCheck 提供开箱即用的功能,然后使用常规函数组合所需的输入。我举个例子。

考虑一下 FsCheck 无法立即生成的这种类型:

type SomeAwkwardType( name: string, id: int, flag: bool ) =
   member this.Name = name
   member this.Id = id
   member this.Flag = flag

这是使用 static-shim-for-type-class 生成器的尴尬方法:

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

下面是生成输入的更直接(在我看来)的方法:

module Tests =
   let [Property] ``Test using straightforward generation`` (name, id, flag) = 
      let input = SomeAwkwardType( name, id, flag )
      someFn input = 42

这不仅更短更清晰,而且还具有这样的优势,即一年后不必再费力地查看代码库以查找实现生成器的静态 class。