Reputation: 1244
I'm finding using specs2 with scalacheck to verify the Monoid laws a bit ugly when trying to make use of the scalaz scalacheck-binding library. My code uses the scalaz Monoid so I wanted to use their laws to verify my MyType implements them.
This uglyness makes me think I'm missing something or mis-using Specs2 or scalacheck-binding API's. Sugestions apreciated.
This is what i've done:-
I'm using specs2 3.7 with scalaz 2.7.0
Reading the user guide at "http://etorreborre.github.io/specs2/guide/SPECS2-3.0/org.specs2.guide.UseScalaCheck.html"
I have extended my spec with the Scalacheck
trait and I have an Arbitrary[MyType]
in scope so I should be able to use scalacheck OK.
The doc mentioned above states that I need to pass a function to the prop
method as long as the passed function returns a Result
where scalacheck's Prop
is a valid Result
The scalacheck-binding api gives me a monoid.laws[T]
function that returns a Properties
which is a Prop
so this should be OK, it also takes implicit parameters of types Monoid[T]
, Equal[T]
and Arbitrary[T]
all of which I have in scope where T
is MyType
I want to do this:
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] }
}
}
but prop
cannot be applied to (org.scalacheck.Properties)
It requires the T in the Arbitrary to be the type in the parameter to the function, so I have done this, notice I trow away the parameter 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] }
}
}
My test passes. yay! So What's the problem?
I'm uneasy about the test. All it says is it passed. I get no output like I would if using Scalacheck directly telling me which laws it ran and passed.
Also I throw away the parameter t
and let monoid.laws[MyType]
find the in scope implicits, which just seems wrong. Is it working? have I mangled the specs2 API?
modifying MyType so it would definatly fail the laws caused the test to fail, which is good but I am still uneasy as it always fails with
Falsified after 0 passed tests.
I can collect the Arbitrary[MyType] by doing
prop { (p: Path) => monoid.laws[Path] }.collectArg(f => "it was " + f.shows)
then running it like so
sbt testOnly MyTypeSpec -- scalacheck.verbose
which shows me the collected values of t
when it works but as I throw away t
I'm not sure if this is valid at all.
Is there a better way to test using Specs2 and the scalaz scalacheck-bindings that is less ugly and outputs info that give me confidence that Laws were tried and tested?
Thanks
Karl
Upvotes: 2
Views: 289
Reputation: 15557
You can use Properties
directly without having to use prop
. Here is a full example:
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)
}
}
And because the Monoid
instance is incorrect it will fail with:
[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
The failure indicates the first laws that fails to pass. It doesn't however create several examples, one for each law, to display which law is being executed. If you want to do that you can map each property of the laws Properties
to an example:
class TestSpec extends Specification with 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 }
}
This prints (for a passing Monoid[PositiveInt]
instance):
[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
Upvotes: 3