Karl
Karl

Reputation: 1244

using specs2 with scalaz-scalacheck-binding to test laws

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

Answers (1)

Eric
Eric

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

Related Questions