我正在使用一段具有广泛/深层案例类层次结构的代码。对于单元测试,我想在类中填充“随机数据”,以便能够更改我关心的字段的数据?
示例:
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")
我听说过ScalaCheck
和Shapeless
和Scalacheck-shapeless
,它们确实提供了某种随机数据生成,但似乎没有提供自定义功能。
我目前正在使用ScalaMock
,但这会扩展null
字段并破坏“其他”测试的可测试性。我在.Net中使用了类似Auto Fixture的工具,并且想知道Scala中是否有类似的工具。
答案 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"
}
}
}