我发现在尝试使用 scalaz scalacheck 绑定(bind)库时,使用 scalacheck 的 specs2 来验证 Monoid 定律有点难看。 我的代码使用 scalaz Monoid,因此我想使用它们的定律来验证我的 MyType 是否实现了它们。
这种丑陋让我觉得我遗漏了一些东西或者误用了 Specs2 或 scalacheck 绑定(bind) 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
方法,只要传递的函数返回一个 Result
,其中 scalacheck 的 Prop
是有效的结果
scalacheck-binding api 给了我一个 monoid.laws[T]
函数,它返回一个 Properties
,它是一个 Prop
所以这应该没关系,它还采用 Monoid[T]
、Equal[T]
和 Arbitrary[T]
类型的隐式参数,所有这些我都有在 T
为 MyType
我想这样做:
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
不能应用于(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.
我可以通过以下方式收集 Arbitrary[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
关于scalaz - 使用 specs2 和 scalaz-scalacheck-binding 来测试法律,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34629024/