在实施基于 属性 的测试时,我应该何时在前提条件表达式上使用输入生成器?

When implementing property-based testing, when should I use an input generator over a precondition expression?

在实施基于 属性 的测试时,我什么时候应该在前置条件表达式上使用输入生成器?

选择特定选项时是否考虑性能?

在内部,一种方法是否不可避免地使用另一种方法?

我认为与输入生成器相比,前置条件表达式的执行时间会更长。有人测试过吗?

为什么我们需要两者?

当您使用前置条件表达式(例如 FsCheck 的 ==> 运算符)时,您实际上是在丢弃数据。即使这种情况只发生在百分之一的情况下,您仍然会为正常 属性 丢弃 1 个输入集(因为在 FsCheck 中默认执行次数为 100)。

扔掉 100 个中的一个可能没什么大不了的。

但是,有时您会丢弃更多数据。例如,如果您只需要正数,您可以写一个像 x > 0 这样的前提条件,但由于 FsCheck 也会生成负数,因此在生成这些值后,您将丢弃所有值的 50%。这可能会使您的测试 运行 变慢(但一如既往,在性能方面的考虑:衡量)。

出于这个原因,FsCheck 带有内置的正数生成器,但有时,您需要对可能的输入值范围进行更细粒度的控制,如 this example.

如果执行 FizzBuzz kata, for example, you may write your test for the FizzBuzz case like this:

[<Property(MaxFail = 2000)>]
let ``FizzBuzz.transform returns FizzBuzz`` (number : int) =
    number % 15 = 0 ==> lazy
    let actual = FizzBuzz.transform number
    let expected = "FizzBuzz"
    expected = actual

注意 MaxFail 属性 的使用。您需要它的原因是因为该先决条件丢弃了 15 个生成的候选人中的 14 个。默认情况下,FsCheck 会在放弃之前尝试 1000 个候选项,但如果您丢弃 15 个候选项中的 14 个,平均而言您将只有 67 个值符合前提条件。由于 FsCheck 的默认目标是执行 属性 100 次,因此它放弃了。

正如 MaxFail 属性 所暗示的,您可以调整默认值。对于 2000 名候选人,您应该期望平均有 133 个前提条件匹配。

虽然感觉效率不是特别高,但您也可以使用自定义生成器:

[<Property(QuietOnSuccess = true)>]
let ``FizzBuzz.transform returns FizzBuzz`` () =
    let fiveAndThrees =
        Arb.generate<int> |> Gen.map ((*) (3 * 5)) |> Arb.fromGen
    Prop.forAll fiveAndThrees <| fun number ->

        let actual = FizzBuzz.transform number

        let expected = "FizzBuzz"
        expected = actual

这使用 an ad-hoc in-line Arbitrary。这样效率更高,因为不会丢弃任何数据。

我倾向于使用先决条件,如果它 only throw away the occasional unmatching input。在大多数情况下,我更喜欢自定义生成器。