5

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

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

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

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

为什么我们需要两者?

4

1 回答 1

9

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

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

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

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

例如,如果执行FizzBu​​zz kata,您可以为FizzBu​​zz案例编写测试,如下所示:

[<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

这使用了一个临时的在线 Arbitrary。这更有效,因为没有数据被丢弃。

我倾向于使用前提条件,如果它只会丢弃偶尔不匹配的输入。在大多数情况下,我更喜欢自定义生成器。

于 2016-03-24T14:03:29.170 回答