在Maven/Gradle项目中,ScalaCheck+ScalaTest能否为Gen设置随机种子?
如何在ScalaTest+ScalaCheck中固定种子复现属性测试失败案例
问题场景
你在Maven/Gradle的Scala项目中使用ScalaTest+ScalaCheck做属性测试时遇到偶发失败,尝试通过Gen.apply(Parameters.default, Seed.apply(12345L))固定随机种子来复现失败案例,但这个操作无效,测试仍然使用随机生成的种子,错误输出如下:
TestFailedException was thrown during property evaluation.
Message: "" was not equal to "1"
Location: (MyTest.scala:21)
Occurred when passed generated values (
arg0 = Foo("", 0)
)
Init Seed: -8607081352353034494
原始测试代码:
import org.scalacheck.{Arbitrary, Gen} import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers._ import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks class MyTest extends AnyFlatSpec with ScalaCheckPropertyChecks { it should "work" in { implicit val arbString = Arbitrary(Gen.alphaStr) implicit val arb: Arbitrary[Foo] = Arbitrary(Gen.resultOf(Foo)) forAll { f: Foo => println(f) f.a shouldBe "1" } } } case class Foo(a: String, b: Int)
你修改后的无效代码:
import org.scalacheck.Gen.Parameters import org.scalacheck.rng.Seed import org.scalacheck.{Arbitrary, Gen} import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers._ import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks class MyTest extends AnyFlatSpec with ScalaCheckPropertyChecks { Gen.apply(Parameters.default, Seed.apply(12345L)) it should "work" in { implicit val arbString = Arbitrary(Gen.alphaStr) implicit val arb: Arbitrary[Foo] = Arbitrary(Gen.resultOf(Foo)) forAll { f: Foo => println(f) f.a shouldBe "1" } } } case class Foo(a: String, b: Int)
为什么你的方法无效
Gen.apply(Parameters.default, Seed.apply(12345L))只是创建了一个单次使用的生成器实例,并没有全局覆盖ScalaTest+ScalaCheck的默认种子配置。forAll方法会使用自身的参数配置逻辑,完全不受这个孤立调用的影响。
正确的解决方法
方法1:针对单个测试用例固定种子(推荐)
直接在forAll调用中传入种子参数,这样只会影响当前测试用例,不会干扰其他测试:
import org.scalacheck.{Arbitrary, Gen} import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers._ import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks._ class MyTest extends AnyFlatSpec with ScalaCheckPropertyChecks { it should "work" in { implicit val arbString = Arbitrary(Gen.alphaStr) implicit val arb: Arbitrary[Foo] = Arbitrary(Gen.resultOf(Foo)) // 使用失败输出中的种子值来复现问题 forAll(initialSeed(-8607081352353034494L)) { f: Foo => println(f) f.a shouldBe "1" } } } case class Foo(a: String, b: Int)
方法2:全局配置种子(适用于整个测试类)
如果需要让测试类中所有forAll都使用固定种子,可以重写scalacheckConfig方法:
import org.scalacheck.{Arbitrary, Gen} import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers._ import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks import org.scalatestplus.scalacheck.PropertyCheckConfiguration class MyTest extends AnyFlatSpec with ScalaCheckPropertyChecks { // 全局设置种子 override val scalacheckConfig = PropertyCheckConfiguration(initialSeed = -8607081352353034494L) it should "work" in { implicit val arbString = Arbitrary(Gen.alphaStr) implicit val arb: Arbitrary[Foo] = Arbitrary(Gen.resultOf(Foo)) forAll { f: Foo => println(f) f.a shouldBe "1" } } } case class Foo(a: String, b: Int)
注意:直接使用错误输出中的
Init Seed值,就能精准复现导致失败的那组测试数据。
内容的提问来源于stack exchange,提问作者tribbloid
相关产品推荐
相关产品推荐

