使用算法生成器进行基于属性的测试:“找到给定序列中不出现的最小正整数”

问题描述

我在使用 ScalaCheck 学习基于属性的 Scala 测试时,偶然发现了有关 stackoverflow 的这一挑战。

Find the smallest positive integer that does not occur in a given sequence

我想尝试为此问题编写一个基于生成器驱动的属性的测试来检查我的程序的有效性,但似乎无法想到如何编写相关的测试用例。我知道我可以为这个用例编写一个基于表驱动的属性的测试,但这限制了我可以用来测试这个算法的属性数量

import scala.annotation.tailrec

object Solution extends App {
  def solution(a: Array[Int]): Int = {
    val posNums = a.toSet.filter(_ > 0)

    @tailrec
    def checkForSmallestNum(ls: Set[Int],nextMin: Int): Int = {
      if (ls.contains(nextMin)) checkForSmallestNum(ls,nextMin + 1)
      else nextMin
    }

    checkForSmallestNum(posNums,1)
  }
}

解决方法

使用 Scalatest 的(因为您标记了 scalatest)Scalacheck 集成和 Scalatest 匹配器,例如

forAll(Gen.listOf(Gen.posNum[Int]) -> "ints") { ints =>
  val asSet = ints.toSet
  val smallestNI = Solution.solution(ints.toArray)
  asSet shouldNot contain(smallestNI)

  // verify that adding non-positive ints doesn't change the result
  forAll(
    Gen.frequency(
      1 -> Gen.const(0),10 -> Gen.negNum[Int]
    ) -> "nonPos"
  ) { nonPos =>
    // Adding a non-positive integer to the input shouldn't affect the result
    Solution.solution((nonPos :: ints).toArray) shouldBe smallestNI
  }

  // More of a property-based approach
  if (smallestNI > 1) {
    forAll(Gen.oneOf(1 until smallestNI) -> "x") { x =>
      asSet should contain(x)
    }
  } else succeed  // vacuous

  // Alternatively,but perhaps in a less property-based way
  (1 until smallestNI).foreach { x =>
    asSet should contain(x)
  }
}

请注意,如果将 scalatest 设置为尝试 forAll s 100 次,则嵌套属性检查将检查值 10k 次。由于 smallestNI 几乎总是小于试验次数(因为 listOf 很少生成长列表),因此详尽检查实际上比嵌套属性检查更快。

总体技巧是,如果某项是某个谓词适用的最小正整数,则等于说对于所有小于该谓词不适用的正整数的所有正整数。