如何获得为scala案例类生成的随机数据,并能够“更改某些值”以进行单元测试?

时间:2019-05-10 19:35:01

标签: scala unit-testing scalacheck

我正在使用一段具有广泛/深层案例类层次结构的代码。对于单元测试,我想在类中填充“随机数据”,以便能够更改我关心的字段的数据?

示例:

case class Foo(bar: Bar, name: String, value: Int)
case class Bar(baz: Baz, price: Double)
case class Baz(thing: String)

类似这样:

val randomFoo = GenerateRandomData(Foo)
randomFoo.bar.baz = Baz("custom for testing")

我听说过ScalaCheckShapelessScalacheck-shapeless,它们确实提供了某种随机数据生成,但似乎没有提供自定义功能。

我目前正在使用ScalaMock,但这会扩展null字段并破坏“其他”测试的可测试性。我在.Net中使用了类似Auto Fixture的工具,并且想知道Scala中是否有类似的工具。

3 个答案:

答案 0 :(得分:4)

我认为,您正在寻找scalaz lense

它会做您想要的。

但是,我得说,使用随机数据进行单元测试似乎是一个可怕的想法。您如何调试时常发生的故障?

您应该花一些时间来建立一组确定性的恒定测试对象,这些对象也类似于实际的生产数据,然后在测试中使用它们。

答案 1 :(得分:2)

Scalacheck确实提供了const生成器,可用于定义自定义/常量字符串:

import org.scalacheck._

  val fooGen: Gen[Foo] =
    for {
      baz <- Gen.const("custom for testing").map(Baz)
      price <- Gen.choose[Double](0, 5000)
      name <- Gen.alphaStr
      value <- Gen.choose(0, 100)
    } yield {
      val bar = Bar(baz, price)
      Foo(bar, name, value)
    }

运行它会得到以下结果:

scala> fooGen.sample
res6: Option[Foo] = Some(
  Foo(
    Bar(Baz("custom for testing"), 1854.3159675078969),
    "EegNcrrQyzuazqrkturrvsqylaauxausrkwtefowpbkptiuoHtdfJjoUImgddhsnjuzpoiVpjAtjzulkMonIrzmfxonBmtZS",
    64
  )
)

更新:正如@Dima所指出的,一种为所有字段导出随机值的方法是将[scalacheck-shapeless]https://github.com/alexarchambault/scalacheck-shapeless)和lenses用于自定义,这是一个使用Monocle的示例:

  import org.scalacheck.{Arbitrary, Gen}
  import monocle.Lens
  import org.scalacheck.ScalacheckShapeless._

  implicitly[Arbitrary[Foo]]

  val lensBar = Lens[Foo, Bar](_.bar)(bar => _.copy(bar = bar))
  val lensBaz = Lens[Bar, Baz](_.baz)(baz => _.copy(baz = baz))
  val lensThing = Lens[Baz, String](_.thing)(thing => _.copy(thing = thing))

  val lens = (lensBar composeLens lensBaz composeLens lensThing).set("custom for testing")


  val fooGen: Gen[Foo] = Arbitrary.arbitrary[Foo].map(lens)


  println(fooGen.sample)
  // Display 
  // Some(Foo(Bar(Baz(custom for testing),1.2227226413326224E-91),〗❌䟤䉲㙯癏<,-2147483648))

答案 2 :(得分:0)

您的问题分为两个部分。可以使用Gen.resultOf在ScalaCheck中自动生成类。先前在scalacheck case class random data generator中提出过此要求。 scalacheck-shapeless可以用更少的样板来完成。

问题的第二部分是关于对不可变对象中的字段进行突变。在大多数情况下,仅使用copy提供的case class构造函数就足够了。像Monocle这样的库可以提供帮助,但我从来不需要使用它。

import org.scalacheck.Arbitrary
import org.scalacheck.Prop
import org.scalacheck.Prop.AnyOperators // Adds ?= operator
import org.scalacheck.Properties
import org.scalacheck.Gen

object FooSpec extends Properties("Foo") {

  val genBaz: Gen[Baz] = Gen.resultOf(Baz)

  implicit val arbBaz = Arbitrary(genBaz)

  val genBar: Gen[Bar] = Gen.resultOf(Bar)

  implicit val arbBar = Arbitrary(genBar)

  val genFoo: Gen[Foo] = Gen.resultOf(Foo)

  implicit val arbFoo = Arbitrary(genFoo)

  val genCustomFoo: Gen[Foo] = {
    Arbitrary.arbitrary[Foo].map { foo =>
      foo.copy(bar = foo.bar.copy(baz = Baz("custom for testing")))
    }
  }

  property("arbFoo") = {
    Prop.forAll { foo: Foo  =>
      foo.bar.baz.thing != "custom for testing"
    }
  }

  property("genCustomFoo") = {
    Prop.forAll(genCustomFoo) { foo: Foo  =>
      foo.bar.baz.thing ?= "custom for testing"
    }
  }
}