使用带有 scalaz-scalacheck-binding 的 specs2 来测试定律

using specs2 with scalaz-scalacheck-binding to test laws

我发现在尝试使用 scalaz scalacheck 绑定库时,使用带有 scalacheck 的 specs2 来验证 Monoid 法则有点难看。 我的代码使用 scalaz Monoid,所以我想使用它们的法则来验证我的 MyType 是否实现了它们。

这种丑陋让我觉得我遗漏了什么或误用了 Specs2 或 scalacheck-binding API。提出建议。

这是我所做的:-

我正在使用 specs2 3.7 和 scalaz 2.7.0

阅读“http://etorreborre.github.io/specs2/guide/SPECS2-3.0/org.specs2.guide.UseScalaCheck.html”处的用户指南 我已经用 Scalacheck 特性扩展了我的规范,并且我有一个 Arbitrary[MyType] 范围,所以我应该能够使用 scalacheck OK.

上面提到的文档指出,我需要将函数传递给 prop 方法,只要传递的函数 returns a Result 其中 scalacheck 的 Prop 是一个有效的 Result

scalacheck-binding api 给了我一个 monoid.laws[T] 函数 returns 一个 Properties 这是一个 Prop 所以这应该没问题,它还采用 Monoid[T]Equal[T]Arbitrary[T] 类型的隐式参数,所有这些我都在范围内,其中 TMyType

我想这样做:

class MyTypeSpec extends Specification with ScalaCheck {
  def is = s2"""
   MyType spec must :-
     obey the Monoid Laws $testMonoidLaws
  """

  def testMonoidLaws = {
    import org.scalacheck.{Gen, Arbitrary}
    import scalaz.scalacheck.ScalazProperties._
    implicit val arbMyType: Arbitrary[MyType] = genArbMyTpe() // an helper Arbitrary Gen func i have written
    prop { monoid.laws[MyType] }
  }
}

但是 prop cannot be applied to (org.scalacheck.Properties) 它要求 Arbitrary 中的 T 是函数参数中的类型,所以我这样做了,注意我丢掉了参数 t,...

class MyTypeSpec extends Specification with ScalaCheck {
  def is = s2"""
   MyType spec must :-
     obey the Monoid Laws $testMonoidLaws
  """

  def testMonoidLaws = {
    import org.scalacheck.{Gen, Arbitrary}
    import scalaz.scalacheck.ScalazProperties._
    implicit val arbMyType: Arbitrary[MyType] = genArbMyTpe() //some Arbitrary Gen func
    prop { (t: Path => monoid.laws[MyType] }
  }
}

我的测试通过了。耶!那么问题是什么?

我对考试感到不安。它只说它通过了。如果使用 Scalacheck 直接告诉我它 运行 并通过了哪些法律,我不会得到任何输出。 我也扔掉了参数 t 并让 monoid.laws[MyType] 找到范围内的隐式,这似乎是错误的。它在工作吗?我弄错了 specs2 API?

修改 MyType 所以它肯定会失败导致测试失败的法律,这很好,但我仍然感到不安,因为它总是失败

Falsified after 0 passed tests.

我可以通过

收集任意[MyType]
prop { (p: Path) => monoid.laws[Path] }.collectArg(f => "it was " + f.shows)

然后运行这样

sbt testOnly MyTypeSpec -- scalacheck.verbose

它向我显示了 t 工作时收集的值,但当我扔掉时 t 我不确定这是否有效。

有没有更好的方法来测试使用 Specs2 和 scalaz scalacheck-bindings,它不那么难看,输出的信息让我相信法律已经过试验和测试?

谢谢

卡尔

你可以直接使用Properties而不需要使用prop。这是一个完整的例子:

import org.specs2._
import scalaz.scalacheck.ScalazProperties._
import org.scalacheck._
import scalaz._, Scalaz._
import PositiveInt._

class TestSpec extends Specification with ScalaCheck { def is = s2"""

 PositiveInt should pass the Monoid laws $e1

"""
  def e1 = monoid.laws[PositiveInt]
}

case class PositiveInt(i: Int)

object PositiveInt {
  implicit def ArbitraryPositiveInt: Arbitrary[PositiveInt] =
    Arbitrary(Gen.choose(0, 100).map(PositiveInt.apply))

  implicit def EqualPositiveInt: Equal[PositiveInt] =
    Equal.equalA[PositiveInt]

  implicit def MonoidPositiveInt: Monoid[PositiveInt] = new Monoid[PositiveInt] {
    val zero = PositiveInt(1)
    def append(p1: PositiveInt, p2: =>PositiveInt): PositiveInt =
      PositiveInt(p1.i + p2.i)
  }
}

并且由于 Monoid 实例不正确,它将失败:

[info] TestSpec
[info]
[error]  x PositiveInt should pass the Monoid laws
[error]  Falsified after 0 passed tests.
[error]  > Labels of failing property:
[error]  monoid.left identity
[error]  > ARG_0: PositiveInt(3)
[info]
[info]
[info] Total for specification TestSpec
[info] Finished in 185 ms
[info] 1 example, 1 failure, 0 error

失败表示第一个法律没有通过。但是,它并没有创建多个示例,每个示例一个,以显示正在执行的是哪部法律。如果你想这样做,你可以将每个 属性 定律 Properties 映射到一个例子: class TestSpec 使用 ScalaCheck 扩展规范 { def is = s2"""

 PositiveInt should pass the Monoid laws $properties

"""

  def properties = toExamples(monoid.laws[PositiveInt])

  def toExamples(ps: Properties): Fragments =
    t ^ Fragments.foreach(ps.properties) { case (name, prop) => br ^ name ! prop }
}

这会打印(对于通过的 Monoid[PositiveInt] 实例):

[info] TestSpec
[info]
[info]  PositiveInt should pass the Monoid laws
[info]   + monoid.semigroup.associative
[info]   + monoid.left identity
[info]   + monoid.right identity
[info]
[info] Total for specification TestSpec
[info] Finished in 91 ms
[info] 3 examples, 300 expectations, 0 failure, 0 error