在ScalaCheck中,我编写了一个非空字符串列表生成器,
val nonEmptyListsOfString: Gen[List[String]] =
Gen.nonEmptyListOf(Arbitrary.arbitrary[String])
然后,假设我使用Prop.forAll
,
Prop.forAll(nonEmptyListsOfString) { strs: List[String] =>
strs == Nil
}
这只是一个失败的简单示例,因此它可以显示Scalacheck如何通过缩小来找到最小的示例。
但是,Scalacheck中的默认收缩器不会respect the generator,并且仍会缩小为空字符串,忽略生成器属性。
sbt> test
[info] ! Prop.isEmpty: Falsified after 1 passed tests.
[info] > ARG_0: List()
[info] > ARG_0_ORIGINAL: List("")
[info] Failed: Total 1, Failed 1, Errors 0, Passed 0
[error] Failed tests:
[error] example.Prop
答案 0 :(得分:0)
如评论中所述,并重新使用您发布的github问题中的示例:
import cats.data.NonEmptyList
import org.scalacheck.{Arbitrary, Gen}
import org.scalatest.{FreeSpec, Matchers}
import org.scalatest.prop.PropertyChecks
class ScalaCheckTest extends FreeSpec with PropertyChecks with Matchers{
"Test scalacheck (failing)" in {
val gen: Gen[List[Int]] = for {
n <- Gen.choose(1, 3)
list <- Gen.listOfN(n, Gen.choose(0, 9))
} yield list
forAll(gen) { list =>
list.nonEmpty shouldBe true
if (list.sum < 18) throw new IllegalArgumentException("ups")
}
}
"Test scalacheck" in {
val gen1 = for{
first <- Arbitrary.arbInt.arbitrary
rest <- Gen.nonEmptyListOf(Arbitrary.arbInt.arbitrary)
} yield {
NonEmptyList(first, rest)
}
forAll(gen1) { list =>
val normalList = list.toList
normalList.nonEmpty shouldBe true
if (normalList.sum < 18) throw new IllegalArgumentException("ups")
}
}
}
第一个测试失败,显示正在使用空列表,但第二个测试确实抛出异常。
更新:显然不需要猫,在这里我使用非空列表的简单(和本地)版本进行此测试。
"Test scalacheck 2" in {
case class FakeNonEmptyList[A](first : A, tail : List[A]){
def toList : List[A] = first :: tail
}
val gen1 = for{
first <- Arbitrary.arbInt.arbitrary
rest <- Gen.nonEmptyListOf(Arbitrary.arbInt.arbitrary)
} yield {
FakeNonEmptyList(first, rest)
}
forAll(gen1) { list =>
val normalList = list.toList
normalList.nonEmpty shouldBe true
if (normalList.sum < 18) throw new IllegalArgumentException("ups")
}
}
答案 1 :(得分:0)
有一种方法可以在ScalaCheck中定义自己的Shrink
类。但是,这并不常见也不容易。
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)
}
}
}
如果您知道如何使用Scala中的Stream
集合,则可以轻松定义Int
的缩小器,该缩小器通过将值减半来缩小:
implicit val intShrinker: Shrink[Int] = Shrink {
case 0 => Stream.empty
case x => Stream.iterate(x / 2)(_ / 2).takeWhile(_ != 0) :+ 0
}
我们希望避免将原始值返回给ScalaCheck,这就是为什么零是特殊情况。
如果是非空字符串列表,则需要重新使用ScalaCheck的容器收缩,但要避免使用空容器。不幸的是,这并不容易,但有可能:
implicit def shrinkListString(implicit s: Shrink[String]): Shrink[List[String]] = Shrink {
case Nil => Stream.empty[List[String]]
case strs => Shrink.shrink(strs)(Shrink.shrinkContainer).filter(!_.isEmpty)
}
上面的那个特定于List[String]
,而不是编写一个避免空容器的通用容器收缩器。它可能会重写为List[T]
。
可能不需要针对Nil
的第一个模式匹配。