ScalaCheck valid/invalid 测试边界

ScalaCheck valid/invalid test boundary

我正在使用 ScalaCheckScalaTest 中做一些基于 属性 的测试。假设我想测试一个函数 f(x: Double): Double,它只为 x >= 0.0 定义,而 returns NaN 用于该域之外的参数。理想情况下,我想做这样的事情:

import org.scalatest.FunSpec
import org.scalatest.prop.GeneratorDrivenPropertyChecks

def f(x: Double) = Math.sqrt(x) // The actual function isn't important.

class FTest
extends FunSpec
with GeneratorDrivenPropertyChecks {
  describe("f(x)") {
    it("must accept every argument value and handle it correctly") {
      forAll { x: Double =>
        val r = f(x)
        if(x >= 0.0) assert(!r.isNaN && r === Math.sqrt(x)) // Too simplistic, I know. ;-)
        else assert(r.isNaN)
      }
    }
  }
}

现在,这相当优雅并且有效,但我担心边界检查,因为我怀疑 - 在一般情况下 - ScalaCheck 是否能够找到边界并测试函数是否正确响应具有该边界两侧的值(在本例中为 >= 0.0)。当然,我可以使用 wheneverScalaTest 替换 ScalaCheck==> 运算符来分离这两个条件), 但那是更多的努力并且浪费了很多生成的值:

class FTest2
extends FunSpec
with GeneratorDrivenPropertyChecks {
  describe("f(x)") {
    it("must accept every valid argument value and handle it correctly") {
      forAll { x: Double =>
        whenever(x >= 0.0) {
          val r = f(x)
          assert(!r.isNaN && r === Math.sqrt(x))
        }
      }
    }
    it("must report the correct error value for invalid argument values") {
      forAll { x: Double =>
        whenever(x < 0.0) assert(f(x).isNaN)
      }
    }
  }
}

(我知道我也可以使用客户生成器来限制范围,这样就不需要 whenever,但我认为这不是重点。如果我错了,请随时纠正我那个。)

所以,我很好奇的是:

  1. 有没有办法向 ScalaCheck 提示边界值是什么,并确保它选择该值及其两侧的值?
  2. 是否有其他替代方案同样优雅,但自动找到边界的效果更好?

感谢您的帮助~

ScalaCheck 无法自动确定您的函数将哪些值视为有效;您需要在您的属性(使用 whenever 之类的东西)或您的生成器中对这些信息进行编码。选择哪种方法是 context-specific.

保持属性 "small" 更可取:集中的正交属性更容易 read/write/maintain,您以后可以随时组合它们以构建更全面的属性。因此,我会将这两个属性(快乐和不快乐的情况)分开。

为了避免 "wasting" 生成值,我会使用两个单独的生成器(一个用于 non-negative 双精度,另一个用于负双精度);这种方法不需要 whenever

val genNonnegativeDouble: Gen[Double] = Gen.choose(0, Double.MaxValue)

val genNegativeDouble: Gen[Double] = Gen.negNum[Double]

您的属性将如下所示:

final class FTest2
    extends FunSpec
    with GeneratorDrivenPropertyChecks {

  describe("f") {
    it("must accept every valid argument value and handle it correctly") {
      forAll(genNonnegativeDouble) { x =>
          val r = f(x)
          assert(!r.isNaN && r === Math.sqrt(x))
      }
    }

    it("must report the correct error value for invalid argument values") {
      forAll(negativeDouble) { x =>
        assert(f(x).isNaN)
      }
    }
  }

}

顺便说一句,

  • 你可能应该 return 不同于 Double.NaN 的东西来表示失败; Option[Double] 是一个很好的候选人,因为失败的原因只有一个。
  • 您应该只检查 近似 相等的双打。