代码之家  ›  专栏  ›  技术社区  ›  Michael

如何创建case类的随机实例?

  •  8
  • Michael  · 技术社区  · 7 年前

    假设我有几个案例类,例如:

    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 ?

    2 回复  |  直到 7 年前
        1
  •  9
  •   Yuval Itzchakov    7 年前

    最简单的方法就是 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 从而产生 根[阿] 对你来说,完全随机的值(你不能控制它们)。

    你可能还想调查一下 random-data-generator (感谢“加布里埃尔·彼得罗内拉”),这进一步简化了事情。从文档中:

    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 .

        2
  •  1
  •   Tanin    6 年前

    我们刚刚从ScalaKeH中移开,并使用Scala/Java反射代替。

    主要原因是(1)scalacheck shapeless使用宏(慢编译),(2)api比我喜欢的要详细一些,(3)生成的值太野(例如,生成带有日语字符的字符串)。

    不过,设置它要复杂一些。下面是一个完整的工作代码,您可以将其复制到您的代码库中:

    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")
    

    上面的代码生成任意且唯一的值。它们并不是完全随机的。它最适合在测试中使用。

    请在此处阅读我们的完整博客文章: https://give.engineering/2018/08/24/instantiate-case-class-with-arbitrary-value.html

    推荐文章