在实现基于属性的测试时,我什么时候应该在前置条件表达式上使用输入生成器
选择特定选项时是否考虑性能因素?
在内部,一种方法是否不可避免地使用另一种方法?
我认为,与输入生成器相比,前置条件表达式的执行时间会更长。有人测试过这个吗?
为什么我们两者都需要?
当使用前置条件表达式(例如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
这使用了一个特别的内联任意。这样做效率更高,因为不会丢弃任何数据。
我倾向于使用前提条件,如果它只会丢弃偶尔不匹配的输入。在大多数情况下,我更喜欢自定义生成器。