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

时间:2016-03-24 00:38:36

标签: f# fscheck property-based-testing

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

选择特定选项时是否存在性能问题?

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

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

为什么我们需要两者?

1 个答案:

答案 0 :(得分:8)

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

扔掉100分中的一个可能不是什么大问题。

然而,有时候,你会丢掉更多的数据。例如,如果您只想要正数,则可以编写x > 0之类的前置条件,但由于FsCheck也会生成负数,因此在生成后,您将丢弃所有值的50%。这可能会使您的测试运行速度变慢(但与性能考虑因素一样:测量)。

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

例如,如果执行FizzBuzz kata,您可以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。在大多数情况下,我更喜欢自定义生成器。