为什么我使用自定义生成器的 Scalacheck 测试在丢弃许多案例后失败了,我该如何解决这个问题?

Why is my Scalacheck test with a custom Generator failing after discarding many cases, and how do I fix this?

我是 Scala 的新手,我正在编写我的第一个 Scalacheck 套件。

我的程序中有一个数据结构,它本质上看起来像一个 (List[Double], List[Double]),只有当 _1 的每个元素都严格大于 [=14= 的相应元素时,它才是良构的].

由于它在实践中稍微复杂一些(尽管为了这个 MWE 的目的,我们可以假装它包含所有内容),我已经为它编写了一个自定义生成器。

然后我添加了两个简单的测试(包括最简单的测试,1 == 1),在这两种情况下测试都失败了,并显示消息Gave up after only XX passed tests. YYY tests were discarded.

为什么会这样,我该如何解决?

附件是我的测试套件和输出。


package com.foo.bar

import org.scalacheck._
import Prop._
import Arbitrary._

object FooSpecification extends Properties("FooIntervals") {

  type FooIntervals = (List[Double], List[Double])

  /* This is supposed to be a tuple of lists s.t. each element of _1
   *  is < the corresponding element of _2
   * 
   *  e.g. (List(1,3,5), List(2,4,6))
   */

  implicit def arbInterval : Arbitrary[FooIntervals] =
    Arbitrary {
      /**
        * Yields a pair (low, high) s.t. low < high
        */
      def GenPair : Gen[(Double, Double)] = for {
        low <- arbitrary[Double]
        high <- arbitrary[Double].suchThat(_ > low)
      } yield (low, high)

      /**
        * Yields (List(x_1,...,x_n), List(y_1,...,y_n))
        * where x_i < y_i forall i and 1 <= n < 20
        */
      for {
        n <- Gen.choose(1,20)
        pairs : List[(Double, Double)] <- Gen.containerOfN[List, (Double, Double)](n, GenPair)
      } yield ((pairs.unzip._1, pairs.unzip._2))
    }

  property("1 == 1") = forAll {
    (b1: FooIntervals)
    =>
    1 == 1
  }

  property("_1.head < _2.head") = forAll {
    (b1: FooIntervals)
    =>
    b1._1.head < b1._2.head
  }
}

[info] ! FooIntervals.1 == 1: Gave up after only 32 passed tests. 501 tests were discarded.
[info] ! FooIntervals._1.head < _2.head: Gave up after only 28 passed tests. 501 tests were discarded.
[info] ScalaTest
[info] Run completed in 1 second, 519 milliseconds.
[info] Total number of tests run: 0
[info] Suites: completed 0, aborted 0
[info] Tests: succeeded 0, failed 0, canceled 0, ignored 0, pending 0
[info] No tests were executed.
[error] Failed: Total 1, Failed 1, Errors 0, Passed 0
[error] Failed tests:
[error]     com.foo.bar.FooSpecification

arbitrary[Double].suchThat(_ > low)这是你的问题。 suchThat 将丢弃所有条件为假的情况。您正在取两个随机值并丢弃其中一个值大于另一个值的所有情况,这将是很多。您可以使用 retryUntil 而不是 suchThat ,这将生成新值直到满足条件而不是丢弃值,但这有可能花费很长时间甚至永远循环的缺点如果条件非常不太可能(想象一下,如果 low 的值非常高,您可能会循环很长时间以获得比它更大的高值,或者永远循环,如果您足够不幸,可能的最大双倍值很低.

有效的是 Gen.choose(low, Double.MaxValue),它将 select 一个介于 lowDouble.MaxValue 之间的值(可能的最大双倍数)。

使用 chooseoneOf 等方法将您的生成器限制为仅 select 您想要的值通常比生成任何可能的任意值并丢弃或重试无效情况要好.仅当与总可能性相比只有极少数情况不符合您的标准时才应这样做,并且使用这些方法不容易定义有效情况。