我想暂时使我的 specs2 测试套件中的 ScalaCheck 属性测试具有确定性,以简化调试。现在,每次我重新运行测试套件时都可能生成不同的值,这让调试变得令人沮丧,因为您不知道观察到的行为的变化是由您的代码更改引起的,还是仅仅由生成的不同数据引起的。
我怎样才能做到这一点?有没有官方的方法来设置 ScalaCheck 使用的随机种子?
我sbt
用来运行测试套件。
奖励问题:是否有官方方法可以打印出ScalaCheck 使用的随机种子,以便您甚至可以重现非确定性测试运行?
我想暂时使我的 specs2 测试套件中的 ScalaCheck 属性测试具有确定性,以简化调试。现在,每次我重新运行测试套件时都可能生成不同的值,这让调试变得令人沮丧,因为您不知道观察到的行为的变化是由您的代码更改引起的,还是仅仅由生成的不同数据引起的。
我怎样才能做到这一点?有没有官方的方法来设置 ScalaCheck 使用的随机种子?
我sbt
用来运行测试套件。
奖励问题:是否有官方方法可以打印出ScalaCheck 使用的随机种子,以便您甚至可以重现非确定性测试运行?
如果您使用的是纯 ScalaCheck 属性,您应该能够使用Test.Params
该类来更改所使用的 java.util.Random
实例并提供您自己的实例,该实例始终返回相同的一组值:
def check(params: Test.Parameters, p: Prop): Test.Result
[更新]
我刚刚发布了一个新的 specs2-1.12.2-SNAPSHOT,您可以在其中使用以下语法来指定您的随机生成器:
case class MyRandomGenerator() extends java.util.Random {
// implement a deterministic generator
}
"this is a specific property" ! prop { (a: Int, b: Int) =>
(a + b) must_== (b + a)
}.set(MyRandomGenerator(), minTestsOk -> 200, workers -> 3)
作为一般规则,在对非确定性输入进行测试时,您应该尝试在出现故障时在某处回显或保存这些输入。
如果数据很小,您可以将其包含在向用户显示的标签或错误消息中;例如,在 xUnit 风格的测试中:(因为我是 Scala 语法的新手)
testLength(String x) {
assert(x.length > 10, "Length OK for '" + x + "'");
}
如果数据很大,例如自动生成的数据库,您可以将其存储在非易失性位置(例如带有时间戳名称的 /tmp)或显示用于生成它的种子。
下一步很重要:获取该值或种子或其他任何内容,并将其添加到您的确定性回归测试中,以便从现在开始每次都对其进行检查。
你说你想让 ScalaCheck 确定性“暂时”重现这个问题;我说你发现了一个非常适合成为单元测试的错误边缘案例(也许在一些手动简化之后)。
奖励问题:是否有官方方法可以打印出 ScalaCheck 使用的随机种子,以便您甚至可以重现非确定性测试运行?
从specs2-scalacheck
版本开始4.6.0
,这现在是默认行为:
给定测试文件HelloSpec
:
package example
import org.specs2.mutable.Specification
import org.specs2.ScalaCheck
class HelloSpec extends Specification with ScalaCheck {
package example
import org.specs2.mutable.Specification
import org.specs2.ScalaCheck
class HelloSpec extends Specification with ScalaCheck {
s2"""
a simple property $ex1
"""
def ex1 = prop((s: String) => s.reverse.reverse must_== "")
}
build.sbt
配置:
import Dependencies._
ThisBuild / scalaVersion := "2.13.0"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / organization := "com.example"
ThisBuild / organizationName := "example"
lazy val root = (project in file("."))
.settings(
name := "specs2-scalacheck",
libraryDependencies ++= Seq(
specs2Core,
specs2MatcherExtra,
specs2Scalacheck
).map(_ % "test")
)
project/Dependencies
:
import sbt._
object Dependencies {
lazy val specs2Core = "org.specs2" %% "specs2-core" % "4.6.0"
lazy val specs2MatcherExtra = "org.specs2" %% "specs2-matcher-extra" % specs2Core.revision
lazy val specs2Scalacheck = "org.specs2" %% "specs2-scalacheck" % specs2Core.revision
}
从sbt
控制台运行测试时:
sbt:specs2-scalacheck> testOnly example.HelloSpec
您会得到以下输出:
[info] HelloSpec
[error] x a simple property
[error] Falsified after 2 passed tests.
[error] > ARG_0: "\u0000"
[error] > ARG_0_ORIGINAL: "猹"
[error] The seed is X5CS2sVlnffezQs-bN84NFokhAfmWS4kAg8_gJ6VFIP=
[error]
[error] > '' != '' (HelloSpec.scala:11)
[info] Total for specification HelloSpec
要重现该特定运行(即使用相同的种子),您可以seed
从输出中获取并使用命令行传递它scalacheck.seed
:
sbt:specs2-scalacheck>testOnly example.HelloSpec -- scalacheck.seed X5CS2sVlnffezQs-bN84NFokhAfmWS4kAg8_gJ6VFIP=
这会产生与以前相同的输出。
您还可以使用以下方式以编程方式设置种子setSeed
:
def ex1 = prop((s: String) => s.reverse.reverse must_== "").setSeed("X5CS2sVlnffezQs-bN84NFokhAfmWS4kAg8_gJ6VFIP=")
提供的另一种方法是在设置的地方Seed
传递一个隐式:Parameters
seed
package example
import org.specs2.mutable.Specification
import org.specs2.ScalaCheck
import org.scalacheck.rng.Seed
import org.specs2.scalacheck.Parameters
class HelloSpec extends Specification with ScalaCheck {
s2"""
a simple property $ex1
"""
implicit val params = Parameters(minTestsOk = 1000, seed = Seed.fromBase64("X5CS2sVlnffezQs-bN84NFokhAfmWS4kAg8_gJ6VFIP=").toOption)
def ex1 = prop((s: String) => s.reverse.reverse must_== "")
}
对于scalacheck-1.12
此配置有效:
new Test.Parameters {
override val rng = new scala.util.Random(seed)
}
因为scalacheck-1.13
它不再起作用,因为 rng 方法已被删除。有什么想法吗?