如何在FsCheck中生成"复杂"对象?

ben*_*ruk 11 f# fscheck

我想创建一个FsCheck生成器来生成"复杂"对象的实例.复杂,我指的是C#中的现有类,它具有许多子属性和集合.这些属性和集合又需要为它们生成数据.

想象一下,这个课程以Menu儿童系列命名,Dishes并且Drinks(我正在制作它,所以忽略了糟糕的设计).我想做以下事情:

  • 生成可变数量Dishes和可变数量的Drinks.
  • 使用FsCheck API 生成DishDrink实例以填充其属性.
  • Menu使用FsCheck API 在实例上设置一些其他基本属性.

如何为这种类型的实例编写生成器?这是一个坏主意吗?(我是基于财产的测试的新手).我已经阅读了文档,但到目前为止显然未能将其全部内化.

有一个很好的例子来生成记录,但这实际上只生成3个相同类型的值float.

Kur*_*out 8

这不是一个坏主意 - 实际上,这是你能够做到这一点的重点.FsCheck的发电机是完全合成的.

首先请注意,如果你有不可变的对象,其构造函数采用原始类型,就像你的Drink和Dish一样,FsCheck可以生成这些开箱即用(使用反射)

let drinkArb = Arb.from<Drink>
let dishArb = Arb.from<Dish>
Run Code Online (Sandbox Code Playgroud)

应该给你一个任意实例,它是一个生成器(生成一个随机的Drink实例)和一个收缩器(取一个Drink实例并使它'更小' - 这有助于调试,尤其是复合结构,你得到一个小计数器 - 例如,如果您的测试失败).

这会很快崩溃 - 在你的例子中,你可能不想要饮料数量或菜肴数量的负整数.上面的代码会生成负数.有时这很容易修复,如果你的类型实际上只是使用Arb.convert的某种类型的包装器,例如

let drinksArb = Arb.Default.PositiveInt() |> Arb.convert (fun positive -> new Drinks(positive) (fun drinks -> drinks.Amount)
Run Code Online (Sandbox Code Playgroud)

你需要提供往返Arb.convert和presto的转换,这是维持你的不变量的Drinks的新任意实例.当然,其他不变量可能不那么容易维护.

之后,从这两件产品同时生成发电机和收缩器变得有点困难.始终从发电机开始,然后在需要时(如果)需要时收缩机.@ simonhdickson的例子看起来很合理.如果你有上面的任意实例,你可以通过调用.Generator来获取它们的生成器.

let drinksGen = drinksArb.Generator
Run Code Online (Sandbox Code Playgroud)

一旦你有零件生成器(饮料和盘子),你可以确实将它们组合在一起,因为@simonhdickson建议:

let menuGenerator =
    Gen.map3 (fun a b c -> Menu(a,b,c)) (Gen.listOf dishGenerator) (Gen.listOf drinkGenerator) (Arb.generate<int>)
Run Code Online (Sandbox Code Playgroud)

分而治之!总的来看看Gen上的intellisense让你得到一些关于如何组合生成器的想法.