这不是一个坏主意——事实上,这是你能够做到这一点的关键。FsCheck 的生成器是完全组合的。
首先请注意,如果您有其构造函数采用原始类型的不可变对象,例如您的 Drink 和 Dish 的样子,FsCheck 可以开箱即用地生成这些对象(使用反射)
let drinkArb = Arb.from<Drink>
let dishArb = Arb.from<Dish>
应该给你一个 Arbitrary 实例,它是一个生成器(生成一个随机 Drink 实例)和一个收缩器(获取一个 Drink 实例并使其“更小” - 这有助于调试,特别是对于复合结构,你会得到一个小计数器-例如,如果您的测试失败)。
但是,这很快就崩溃了-在您的示例中,您可能不希望饮料数量或菜肴数量为负整数。上面的代码会生成负数。有时,如果您的类型实际上只是另一种类型的某种包装器,则使用 Arb.convert 有时这很容易解决,例如
let drinksArb = Arb.Default.PositiveInt() |> Arb.convert (fun positive -> new Drinks(positive) (fun drinks -> drinks.Amount)
您需要提供到 Arb.convert 和 presto 的往返转换,这是保持您的不变量的 Drinks 的新任意实例。当然,其他不变量可能不那么容易维护。
之后,从这两部分同时生成生成器和收缩器变得有点困难。始终从生成器开始,然后如果(何时)需要它,收缩器会稍后出现。@simonhdickson 的例子看起来很合理。如果您有上述任意实例,您可以通过调用 .Generator 来获取它们的生成器。
let drinksGen = drinksArb.Generator
一旦有了零件生成器(Drink 和 Dish),您确实可以按照@simonhdickson 的建议将它们组合在一起:
let menuGenerator =
Gen.map3 (fun a b c -> Menu(a,b,c)) (Gen.listOf dishGenerator) (Gen.listOf drinkGenerator) (Arb.generate<int>)
分而治之!总的来说,看看 Gen 上的 intellisense 为您提供了一些关于如何组成生成器的想法。