如何创建案例 class 的随机实例?
How to create a random instance of a case class?
假设我有几个案例类,例如:
case class C(c1: Int, c2: Double, c3: Option[String])
case class B(b: Int, cs: Seq[C])
case class A(a: String, bs: Seq[B])
现在我想生成几个 A
的实例,其中包含用于测试的随机值。
我正在寻找一种通用方法来做到这一点。我可能可以通过运行时反射来完成,但我更喜欢编译时解决方案。
def randomInstance[A](a: A): A = ???
我该怎么做?可以用 shapeless
来完成吗?
最简单的方法是使用 ScalaCheck。为此,您可以为您的实例定义一个 Gen[A]
:
import org.scalacheck.Gen
final case class C(c1: Int, c2: Double, c3: Option[String])
object C {
val cGen: Gen[C] = for {
c1 <- Gen.posNum[Int]
c2 <- Gen.posNum[Double]
c3 <- Gen.option(Gen.oneOf("foo", "bar", "hello"))
} yield C(c1, c2, c3)
}
然后你消费它:
object F {
def main(args: Array[String]): Unit = {
val randomC: C = C.cGen.sample.get
}
}
最重要的是,您可以添加 scalacheck-shapeless 为您生成 Gen[A]
,具有完全随机的值(您无法控制它们)。
您可能还想查看 random-data-generator(感谢@Gabriele Petronella),这可以进一步简化事情。来自文档:
import com.danielasfregola.randomdatagenerator.RandomDataGenerator
object MyApp extends RandomDataGenerator {
case class Example(text: String, n: Int)
val example: Example = random[Example]
// Example(ਈ䈦㈾钜㔪旅ꪔ墛炝푰⡨䌆ᵅ퍧咪, 73967257)
}
这在 property based testing 中也特别有用。
我们刚刚放弃了 scalacheck-shapeless,转而使用 Scala/Java 反射。
主要原因是 (1) scalacheck-shapeless 使用宏(编译速度慢),(2) API 比我喜欢的要冗长一点,以及 (3) 生成的值太多了wild(例如生成带有日语字符的字符串)。
但是,设置它有点复杂。这是一个完整的工作代码,您可以将其复制到您的代码库中:
import scala.reflect.api
import scala.reflect.api.{TypeCreator, Universe}
import scala.reflect.runtime.universe._
object Maker {
val mirror = runtimeMirror(getClass.getClassLoader)
var makerRunNumber = 1
def apply[T: TypeTag]: T = {
val method = typeOf[T].companion.decl(TermName("apply")).asMethod
val params = method.paramLists.head
val args = params.map { param =>
makerRunNumber += 1
param.info match {
case t if t <:< typeOf[Enumeration#Value] => chooseEnumValue(convert(t).asInstanceOf[TypeTag[_ <: Enumeration]])
case t if t =:= typeOf[Int] => makerRunNumber
case t if t =:= typeOf[Long] => makerRunNumber
case t if t =:= typeOf[Date] => new Date(Time.now.inMillis)
case t if t <:< typeOf[Option[_]] => None
case t if t =:= typeOf[String] && param.name.decodedName.toString.toLowerCase.contains("email") => s"random-$arbitrary@give.asia"
case t if t =:= typeOf[String] => s"arbitrary-$makerRunNumber"
case t if t =:= typeOf[Boolean] => false
case t if t <:< typeOf[Seq[_]] => List.empty
case t if t <:< typeOf[Map[_, _]] => Map.empty
// Add more special cases here.
case t if isCaseClass(t) => apply(convert(t))
case t => throw new Exception(s"Maker doesn't support generating $t")
}
}
val obj = mirror.reflectModule(typeOf[T].typeSymbol.companion.asModule).instance
mirror.reflect(obj).reflectMethod(method)(args:_*).asInstanceOf[T]
}
def chooseEnumValue[E <: Enumeration: TypeTag]: E#Value = {
val parentType = typeOf[E].asInstanceOf[TypeRef].pre
val valuesMethod = parentType.baseType(typeOf[Enumeration].typeSymbol).decl(TermName("values")).asMethod
val obj = mirror.reflectModule(parentType.termSymbol.asModule).instance
mirror.reflect(obj).reflectMethod(valuesMethod)().asInstanceOf[E#ValueSet].head
}
def convert(tpe: Type): TypeTag[_] = {
TypeTag.apply(
runtimeMirror(getClass.getClassLoader),
new TypeCreator {
override def apply[U <: Universe with Singleton](m: api.Mirror[U]) = {
tpe.asInstanceOf[U # Type]
}
}
)
}
def isCaseClass(t: Type) = {
t.companion.decls.exists(_.name.decodedName.toString == "apply") &&
t.decls.exists(_.name.decodedName.toString == "copy")
}
}
而且,当你想使用它时,你可以调用:
val user = Maker[User]
val user2 = Maker[User].copy(email = "someemail@email.com")
以上代码生成任意且唯一的值。它们并不是完全随机的。最适合在测试中使用。
在此处阅读我们的完整博客 post:https://give.engineering/2018/08/24/instantiate-case-class-with-arbitrary-value.html
我们已经开始使用 Magnolia,与无形派生相比,它提供更快的类型 class 派生来派生任意实例。
Here is the library to use, and here is an example (docs):
case class Inner(int: Int, str: String)
case class Outer(inner: Inner)
// ScalaCheck Arbitrary
import magnolify.scalacheck.auto._
import org.scalacheck._ // implicit instances for Arbitrary[Int], etc.
val arb: Arbitrary[Outer] = implicitly[Arbitrary[Outer]]
arb.arbitrary.sample
// = Some(Outer(Inter(12345, abcde)))
假设我有几个案例类,例如:
case class C(c1: Int, c2: Double, c3: Option[String])
case class B(b: Int, cs: Seq[C])
case class A(a: String, bs: Seq[B])
现在我想生成几个 A
的实例,其中包含用于测试的随机值。
我正在寻找一种通用方法来做到这一点。我可能可以通过运行时反射来完成,但我更喜欢编译时解决方案。
def randomInstance[A](a: A): A = ???
我该怎么做?可以用 shapeless
来完成吗?
最简单的方法是使用 ScalaCheck。为此,您可以为您的实例定义一个 Gen[A]
:
import org.scalacheck.Gen
final case class C(c1: Int, c2: Double, c3: Option[String])
object C {
val cGen: Gen[C] = for {
c1 <- Gen.posNum[Int]
c2 <- Gen.posNum[Double]
c3 <- Gen.option(Gen.oneOf("foo", "bar", "hello"))
} yield C(c1, c2, c3)
}
然后你消费它:
object F {
def main(args: Array[String]): Unit = {
val randomC: C = C.cGen.sample.get
}
}
最重要的是,您可以添加 scalacheck-shapeless 为您生成 Gen[A]
,具有完全随机的值(您无法控制它们)。
您可能还想查看 random-data-generator(感谢@Gabriele Petronella),这可以进一步简化事情。来自文档:
import com.danielasfregola.randomdatagenerator.RandomDataGenerator
object MyApp extends RandomDataGenerator {
case class Example(text: String, n: Int)
val example: Example = random[Example]
// Example(ਈ䈦㈾钜㔪旅ꪔ墛炝푰⡨䌆ᵅ퍧咪, 73967257)
}
这在 property based testing 中也特别有用。
我们刚刚放弃了 scalacheck-shapeless,转而使用 Scala/Java 反射。
主要原因是 (1) scalacheck-shapeless 使用宏(编译速度慢),(2) API 比我喜欢的要冗长一点,以及 (3) 生成的值太多了wild(例如生成带有日语字符的字符串)。
但是,设置它有点复杂。这是一个完整的工作代码,您可以将其复制到您的代码库中:
import scala.reflect.api
import scala.reflect.api.{TypeCreator, Universe}
import scala.reflect.runtime.universe._
object Maker {
val mirror = runtimeMirror(getClass.getClassLoader)
var makerRunNumber = 1
def apply[T: TypeTag]: T = {
val method = typeOf[T].companion.decl(TermName("apply")).asMethod
val params = method.paramLists.head
val args = params.map { param =>
makerRunNumber += 1
param.info match {
case t if t <:< typeOf[Enumeration#Value] => chooseEnumValue(convert(t).asInstanceOf[TypeTag[_ <: Enumeration]])
case t if t =:= typeOf[Int] => makerRunNumber
case t if t =:= typeOf[Long] => makerRunNumber
case t if t =:= typeOf[Date] => new Date(Time.now.inMillis)
case t if t <:< typeOf[Option[_]] => None
case t if t =:= typeOf[String] && param.name.decodedName.toString.toLowerCase.contains("email") => s"random-$arbitrary@give.asia"
case t if t =:= typeOf[String] => s"arbitrary-$makerRunNumber"
case t if t =:= typeOf[Boolean] => false
case t if t <:< typeOf[Seq[_]] => List.empty
case t if t <:< typeOf[Map[_, _]] => Map.empty
// Add more special cases here.
case t if isCaseClass(t) => apply(convert(t))
case t => throw new Exception(s"Maker doesn't support generating $t")
}
}
val obj = mirror.reflectModule(typeOf[T].typeSymbol.companion.asModule).instance
mirror.reflect(obj).reflectMethod(method)(args:_*).asInstanceOf[T]
}
def chooseEnumValue[E <: Enumeration: TypeTag]: E#Value = {
val parentType = typeOf[E].asInstanceOf[TypeRef].pre
val valuesMethod = parentType.baseType(typeOf[Enumeration].typeSymbol).decl(TermName("values")).asMethod
val obj = mirror.reflectModule(parentType.termSymbol.asModule).instance
mirror.reflect(obj).reflectMethod(valuesMethod)().asInstanceOf[E#ValueSet].head
}
def convert(tpe: Type): TypeTag[_] = {
TypeTag.apply(
runtimeMirror(getClass.getClassLoader),
new TypeCreator {
override def apply[U <: Universe with Singleton](m: api.Mirror[U]) = {
tpe.asInstanceOf[U # Type]
}
}
)
}
def isCaseClass(t: Type) = {
t.companion.decls.exists(_.name.decodedName.toString == "apply") &&
t.decls.exists(_.name.decodedName.toString == "copy")
}
}
而且,当你想使用它时,你可以调用:
val user = Maker[User]
val user2 = Maker[User].copy(email = "someemail@email.com")
以上代码生成任意且唯一的值。它们并不是完全随机的。最适合在测试中使用。
在此处阅读我们的完整博客 post:https://give.engineering/2018/08/24/instantiate-case-class-with-arbitrary-value.html
我们已经开始使用 Magnolia,与无形派生相比,它提供更快的类型 class 派生来派生任意实例。
Here is the library to use, and here is an example (docs):
case class Inner(int: Int, str: String)
case class Outer(inner: Inner)
// ScalaCheck Arbitrary
import magnolify.scalacheck.auto._
import org.scalacheck._ // implicit instances for Arbitrary[Int], etc.
val arb: Arbitrary[Outer] = implicitly[Arbitrary[Outer]]
arb.arbitrary.sample
// = Some(Outer(Inter(12345, abcde)))