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

Sco*_*rod 4 f# fscheck property-based-testing

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

选择特定选项时是否存在性能因素?

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

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

为什么我们需要两者?

Mar*_*ann 8

当您使用前置条件表达式(例如FsCheck的==>运算符)时,您实际上是丢弃数据.即使这只发生在一百个案例中的一个案例中,你仍然会丢弃一个普通属性的输入集(因为在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
Run Code Online (Sandbox Code Playgroud)

注意使用该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
Run Code Online (Sandbox Code Playgroud)

这使用ad-hoc在线任意.这样更有效,因为没有数据被丢弃.

我倾向于使用先决条件,如果它只是丢掉偶尔的不匹配输入.在大多数情况下,我更喜欢定制生成器.