【问题标题】:Scalacheck won't properly report the failing caseScalacheck 不会正确报告失败的情况
【发布时间】:2013-12-01 00:28:18
【问题描述】:

我已经编写了以下规范

"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)
    }
  }
}

【问题讨论】:

    标签: unit-testing scala automated-tests scalatest scalacheck


    【解决方案1】:

    这是由 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/89,收缩器实际上不再尊重listOfN 列表长度。希望这可以在未来版本的 ScalaCheck 中得到修复。

    【讨论】:

    • forAllNoShrink 非常适合我的场景。诀窍是 scalatest 没有像 forAll 那样提供辅助方法,因此需要进行更多调查。谢谢。
    • @rickard_nilsson:使用您提供的listOfN/choose 组合示例时,我无法验证ScalaCheck 1.11.5 是否尊重Gen 的边界;见pastebin.com/YuHy9r6d。
    • @ErikAllik 你说得对,containerOfN 实现实际上存在一个错误,因此必须删除列表长度检查。希望它可以在 ScalaCheck 的未来版本中回归。见github.com/rickynils/scalacheck/issues/89
    • ...添加支票有问题吗? :)
    • 请注意,在这种情况下,像 (Byte, Byte, Byte, Byte) 这样的元组将起作用,或者只接受四个 Byte 参数。 Scala 的 Byte 已签名,因此需要强制转换和递增 128,这将降低错误消息的用处。或者,choose(0, 255).suchThat(n =&gt; n &gt;= 0 &amp;&amp; n &lt; 256) 可以代替 Byte,并提供更多有用的错误消息。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-07-02
    • 2019-08-16
    • 2017-06-11
    • 2018-08-30
    • 1970-01-01
    • 1970-01-01
    • 2020-12-12
    相关资源
    最近更新 更多