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



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

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

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

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

为什么我们两者都需要?

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

丢掉百分之一可能不是什么大不了的事。

然而,有时你会扔掉更多的数据。例如,如果你只想要正数,你可以写一个像x > 0这样的先决条件,但由于FsCheck也会生成负数,你会在生成所有值后丢弃50%的值。这可能会使测试运行速度变慢(但与往常一样,当涉及到性能考虑时:度量)。

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

例如,如果进行FizzBuzz卡塔,您可以为FizzBuzz情况编写测试,如下所示:

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

这使用了一个特别的内联任意。这样做效率更高,因为不会丢弃任何数据。

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

最新更新