在ScalaCheck中,我进行了一个属性测试,该测试使用正整数生成器,当它失败时,ScalaCheck将缩小为非正值。
收缩应该有助于找到最小的失败案例。缩小到所需范围之外的值会造成混乱和无益。这是一个已知的错误,请参见ScalaCheck issue #129 Gen.suchThat not respected by shrinking
是否可以在范围内定义自己的收缩实例,使其仅收缩为正整数?
这是一个最小的属性测试:
class ShrinkProp extends Properties("Shrink") {
property("posNum[Int]") = {
Prop.forAll(Gen.posNum[Int]) { _: Int =>
Prop.falsified
}
}
}
这通常会导致ScalaCheck将参数缩小为零:
[info] Done compiling. [info] ! Shrink.posNum[Int]: Falsified after 0 passed tests. [info] > ARG_0: 0 [info] > ARG_0_ORIGINAL: 1 [info] Failed: Total 1, Failed 1, Errors 0, Passed 0
或更糟糕的是,有时它可能缩小为负值:
[info] ! Shrink.posNum[Int]: Falsified after 5 passed tests. [info] > ARG_0: -1 [info] > ARG_0_ORIGINAL: 3 [info] Failed: Total 1, Failed 1, Errors 0, Passed 0
一种解决方案是用forAllNoShrink
来缩小收缩:
class ShrinkProp extends Properties("Shrink") {
property("posNum[Int]") = {
Prop.forAllNoShrink(Gen.posNum[Int]) { _: Int =>
Prop.falsified
}
}
}
结果没有缩小:
[info] ! Shrink.posNum[Int]: Falsified after 0 passed tests. [info] > ARG_0: 1 [info] Failed: Total 1, Failed 1, Errors 0, Passed 0
另一种替代方法是在测试中添加防护,以使他缩小值或跳过:
import Prop.BooleanOperators
class ShrinkProp extends Properties("Shrink") {
property("posNum[Int]") = {
Prop.forAll(Gen.posNum[Int]) { x: Int =>
(x >= 1) ==> Prop.falsified
}
}
}
是否有禁用收缩和增加防护的替代方法?
答案 0 :(得分:0)
ScalaCheck中的正整数没有收缩。你必须自己写。
在您的属性测试范围内,需要将Shrink
定义为implicit
。然后Prop.forAll
将找到正确的Shrink
类,如果它在范围内,并且具有通过测试失败的值的适当类型签名。
从根本上讲,Shrink
实例是将失败值x
转换为“收缩”值流的函数。它的类型签名大致为:
trait Shrink[T] {
def shrink(x: T): Stream[T]
}
您可以使用伴随对象的Shrink
方法定义apply
,大致是这样:
object Shrink {
def apply[T](s: T => Stream[T]): Shrink[T] = {
new Shrink[T] {
def shrink(x: T): Stream[T] = s(x)
}
}
}
正整数的收缩器是Stream
,它通过将值减半以通过二进制搜索找到最小的失败情况而收缩,但是在达到零之前停止:
class ShrinkProp extends Properties("Shrink") {
implicit val posIntShrinker: Shrink[Int] = Shrink { x: Int =>
Stream.iterate(x / 2) { x: Int =>
x / 2
}.takeWhile { x: Int =>
x > 0 // Avoid zero.
}
}
property("posNum[Int]") = {
Prop.forAll(Gen.posNum[Int]) { _: Int =>
Prop.falsified
}
}
}
证明故障有效:
[info] ! Shrink.posNum[Int]: Falsified after 6 passed tests. [info] > ARG_0: 2 [info] > ARG_0_ORIGINAL: 4 [info] Failed: Total 1, Failed 1, Errors 0, Passed 0
更好的是,您可以编写一个属性来验证收缩器的行为是否正确:
property("posIntShrinker") = {
Prop.forAll { x: Int =>
val shrunk = Shrink.shrink(x)
Prop.atLeastOne(
(x >= 2) ==> shrunk.size > 0,
(x <= 1) ==> shrunk.isEmpty
)
}
}
[info] + Shrink.posIntShrinker: OK, passed 100 tests. [info] Failed: Total 1, Failed 0, Errors 0, Passed 1
写一个通用的正数Shrink
可以缩小其他类型的数字,例如Long
,浮点数和BigDecimal
,这是很好的。