你为什么需要在scalacheck中使用Arbitraries?

22 random scala scalacheck

我想知道为什么需要Arbitrary,因为自动化属性测试需要属性定义,比如

val prop = forAll(v: T => check that property holds for v)
Run Code Online (Sandbox Code Playgroud)

和价值v发电机.用户指南说您可以为自定义类型创建自定义生成器(例如,树的生成器).然而,它并没有解释为什么你需要最重要的仲裁.

这是一本手册

implicit lazy val arbBool: Arbitrary[Boolean] = Arbitrary(oneOf(true, false))
Run Code Online (Sandbox Code Playgroud)

要获得对您自己的类型T的支持,您需要定义一个类型为Arbitrary [T]的隐式def或val.使用工厂方法Arbitrary(...)创建Arbitrary实例.此方法采用Gen [T]类型的一个参数并返回Arbitrary [T]的实例.

它清楚地表明我们需要在Gen之上的任意.但任意的理由并不令人满意

任意生成器是ScalaCheck在为属性参数生成值时使用的生成器.

IMO,要使用生成器,您需要导入它们而不是将它们包装到仲裁中!否则,我们可以争辩说我们需要将仲裁包装到其他东西中以使它们可用(等等无限期地无限地包装包装器).

您还可以解释如何arbitrary[Int]将参数类型转换为生成器.这很奇怪,我觉得这些是相关的问题.

Ric*_*son 21

forAll { v: T => ... }在Scala implicits的帮助下实现.这意味着可以T隐式找到该类型的生成器,而不是由调用者明确指定.

Scala隐含很方便,但如果您不确定当前隐含值或转换的范围,它们也会很麻烦.通过使用特定类型(Arbitrary)进行隐式查找,ScalaCheck尝试约束使用implicits的负面影响(这种使用也使其类似于某些用户熟悉的Haskell 类型类).

所以,你完全正确,Arbitrary并不是真的需要.可以通过隐含Gen[T]值实现相同的效果,可以说是更隐含的范围混淆.

作为最终用户,您应该将其Arbitrary[T]视为该类型的默认生成器T.您可以(通过作用域)定义和使用多个Arbitrary[T]实例,但我不推荐它.相反,只需跳过Arbitrary并明确指定您的生成器:

val myGen1: Gen[T] = ...
val mygen2: Gen[T] = ...

val prop1 = forAll(myGen1) { t => ... }
val prop2 = forAll(myGen2) { t => ... }

arbitrary[Int]就像forAll { n: Int => ... },它只是查找隐式Arbitrary[Int]实例并使用它的生成器.实现很简单:

def arbitrary[T](implicit a: Arbitrary[T]): Gen[T] = a.arbitrary

这里的实施Arbitrary可能也会有所帮助:

sealed abstract class Arbitrary[T] {
  val arbitrary: Gen[T]
}

  • 等待。我错过了为什么任意变量比生成器更容易消除歧义。 (2认同)

Bru*_*eth 10

ScalaCheck已从Haskell QuickCheck库中移植.在Haskell类型类中,只允许给定类型的一个实例,从而迫使您进入这种分离.但是在Scala中,没有这样的约束,可以简化库.我的猜测是,ScalaCheck(最初编写为)QuickCheck的1-1映射,使Haskellers更容易进入Scala :)

这是Haskell对任意的定义

class Arbitrary a where
  -- | A generator for values of the given type.
  arbitrary :: Gen a
Run Code Online (Sandbox Code Playgroud)

和Gen

newtype Gen a
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,它们有一个非常不同的语义,Arbitrary是一个类型类,而Gen是一个带有一堆组合器的包装器来构建它们.

我同意"通过语义限制范围"的论点有点模糊,在组织代码时似乎没有被认真对待:任意类有时只是简单地委托给Gen实例.

/** Arbirtrary instance of Calendar */
implicit lazy val arbCalendar: Arbitrary[java.util.Calendar] =
  Arbitrary(Gen.calendar)
Run Code Online (Sandbox Code Playgroud)

有时会定义自己的发电机

/** Arbitrary BigInt */
implicit lazy val arbBigInt: Arbitrary[BigInt] = {
  val long: Gen[Long] =
    Gen.choose(Long.MinValue, Long.MaxValue).map(x => if (x == 0) 1L else x)

  val gen1: Gen[BigInt] = for { x <- long } yield BigInt(x)
  /* ... */

  Arbitrary(frequency((5, gen0), (5, gen1), (4, gen2), (3, gen3), (2, gen4)))
}
Run Code Online (Sandbox Code Playgroud)

所以实际上这会导致代码重复(每个默认的Gen都被一个任意的镜像)和一些混淆(为什么Arbitrary[BigInt]不包装默认值Gen[BigInt]?).