我正在使用 ScalaCheck 在 ScalaTest 中进行一些基于属性的测试。假设我想测试一个仅为f(x: Double): Double
定义的函数x >= 0.0
,并为该域外的参数返回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)。当然,我可以使用whenever
( ScalaTest 替换 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
,但我认为这不是重点。如果我错了,请随意纠正我。 )
所以,我很好奇的是:
感谢您的帮助〜
答案 0 :(得分:2)
ScalaCheck无法自动确定您的功能视为有效的值;您需要在属性中编码此信息(使用类似whenever
的内容)或在您的生成器中编码。选择哪种方法是特定于上下文的。
保持属性“小”是优选的:聚焦,正交属性更易于读/写/维护,您可以随后组合它们以构建更全面的属性。因此,我会将两个属性(快乐和不快乐的情况)分开。
为了避免“浪费”生成的值,我会使用两个独立的生成器(一个用于非负双打,另一个用于负双打);这种方法不需要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)
}
}
}
}
顺便提及,
Double.NaN
以外的其他内容来表示失败; Option[Double]
是一个很好的候选人,因为只有一个失败原因。