12

我写了以下规范

"An IP4 address" should "belong to just one class" in {
    val addrs = for {
        a <- Gen.choose(0, 255)
        b <- Gen.choose(0, 255)
        c <- Gen.choose(0, 255)
        d <- Gen.choose(0, 255)
    } yield s"$a.$b.$c.$d"

    forAll (addrs) { ip4s =>
        var c: Int = 0
        if (IP4_ClassA.unapply(ip4s).isDefined) c = c + 1
        if (IP4_ClassB.unapply(ip4s).isDefined) c = c + 1
        if (IP4_ClassC.unapply(ip4s).isDefined) c = c + 1
        if (IP4_ClassD.unapply(ip4s).isDefined) c = c + 1
        if (IP4_ClassE.unapply(ip4s).isDefined) c = c + 1
        c should be (1)
    }
}

这在其范围内非常清楚。

测试成功通过,但是当我强制它失败时(例如通过注释掉其中一个if语句),ScalaCheck 会正确报告错误,但消息没有正确提及用于评估命题的实际值。更具体地说,我得到:

[info] An IP4 address
[info] - should belong to just one class *** FAILED ***
[info]   TestFailedException was thrown during property evaluation.
[info]     Message: 0 was not equal to 1
[info]     Location: (NetSpec.scala:105)
[info]     Occurred when passed generated values (
[info]       arg0 = "" // 4 shrinks
[info]     )

你可以看到arg0 = "" // 4 shrinks的地方没有显示价值。

我什至尝试添加一个简单的println语句来审查案例,但输出似乎被修剪了。我得到这样的东西

192.168.0.1
189.168.
189.
1

解决方案

import org.scalacheck.Prop.forAllNoShrink
import org.scalatest.prop.Checkers.check

"An IP4 address" should "belong to just one class" in {
  val addrs = for {
    a <- Gen.choose(0, 255)
    b <- Gen.choose(0, 255)
    c <- Gen.choose(0, 255)
    d <- Gen.choose(0, 255)
  } yield s"$a.$b.$c.$d"
  check {
    forAllNoShrink(addrs) { ip4s =>
      var c: Int = 0
      if (IP4.ClassA.unapply(ip4s).isDefined) c = c + 1
      if (IP4.ClassB.unapply(ip4s).isDefined) c = c + 1
      if (IP4.ClassC.unapply(ip4s).isDefined) c = c + 1
      if (IP4.ClassD.unapply(ip4s).isDefined) c = c + 1
      if (IP4.ClassE.unapply(ip4s).isDefined) c = c + 1
      c == (1)
    }
  }
}
4

1 回答 1

12

这是由 ScalaCheck 的测试用例简化特性引起的。ScalaCheck 只是看到你的生成器产生了一个字符串值。每当它找到一个使您的属性为假的值时,它都会尝试简化该值。在您的情况下,它简化了四次,直到它以一个空字符串结束,这仍然使您的属性为假。

所以这是预期的,虽然令人困惑,行为。但是您可以通过三种不同的方式改善这种情况。

您可以选择其他数据结构来表示您的 IP 地址。这将使 ScalaCheck 能够以更智能的方式简化您的测试用例。例如,使用以下生成器:

val addrs = Gen.listOfN(4, Gen.choose(0,255))

现在 ScalaCheck 知道您的生成器只生成长度为 4 的列表,并且它只包含 0 到 255 之间的数字。测试用例简化过程将考虑到这一点,并且不会创建任何生成器无法生成的值开始。您可以改为在属性内转换为字符串。

第二种方法是直接向生成器添加一个过滤器,它告诉 ScalaCheck IP 地址字符串应该是什么样子。此过滤器在测试用例简化期间使用。定义一个检查有效字符串的函数,并以这种方式将其附加到现有生成器:

def validIP(ip: String): Boolean = ...

val validAddrs = addrs.suchThat(validIP)

forAll(validAddrs) { ... }

forAllNoShrink第三种方法是通过使用而不是完全禁用测试用例简化功能forAll

Prop.forAllNoShrink(addrs) { ... }

我还应该提到,前两种方法需要 ScalaCheck 版本 >= 1.11.0 才能正常运行。

更新:

由于https://github.com/rickynils/scalacheck/issues/89listOfN,收缩器实际上不再尊重列表长度。希望这可以在未来版本的 ScalaCheck 中修复。

于 2013-11-18T23:59:48.733 回答