【问题标题】:How does one generate a "complex" object in FsCheck?如何在 FsCheck 中生成“复杂”对象?
【发布时间】:2014-02-26 00:08:07
【问题描述】:

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

想象一下这个类被命名为Menu 与子集合DishesDrinks(我正在编造这个,所以忽略糟糕的设计)。我想做以下事情:

  • 生成变量号Dishes和变量号Drinks
  • 使用 FsCheck API 生成 DishDrink 实例以填充它们的属性。
  • 使用 FsCheck API 在Menu 实例上设置一些其他原始属性。

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

有一个不错的example for generating a record,但这实际上只生成了3个相同类型的值float

【问题讨论】:

标签: f# fscheck


【解决方案1】:

这不是一个坏主意——事实上,你能够做到这一点很重要。 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 为您提供了一些关于如何组合生成器的想法。

【讨论】:

【解决方案2】:

可能有更好的描述方式,但我认为这可能符合您的想法。每个Drink/Dish 类型都可以使用与menuGenerator 相同的样式来获取更多参数

type Drink() =
    member m.X = 1

type Dish() =
    member m.Y = 2

type Menu(dishes:Dish list, drinks:Drink list, total:int) =
    member m.Dishes = dishes
    member m.Drinks = drinks
    member m.Total = total

let drinkGenerator = Arb.generate<unit> |> Gen.map (fun () -> Drink())
let dishGenerator = Arb.generate<unit> |> Gen.map (fun () -> Dish())
let menuGenerator =
    Gen.map3 (fun a b c -> Menu(a,b,c)) <| Gen.listOf dishGenerator <| Gen.listOf drinkGenerator <| Arb.generate<int>

【讨论】:

  • generate&lt;unit&gt;, map3... 不必要的复杂恕我直言。
  • 没错,你发布的链接示例远胜于此。
猜你喜欢
  • 1970-01-01
  • 2014-11-02
  • 2015-11-25
  • 1970-01-01
  • 2012-10-05
  • 2018-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多