Migration From ScalaCheck
Migration From ScalaCheck
For many cases migrating from ScalaCheck to Hedgehog should be fairly straight forward, as the general principals are quite similar, and the changes are largely syntactic.
Properties
Some basic rules:
- Replace
Properties("...")with justProperties - Replace
Prop.forAllwith a call to forAll on a specificGeninstance flatMapover the result of yourgenFoo.forAll, or use aforcomprehension.- Return your
ProporBooleanassetions withResult.assert(...) - Replace label or
:|with Result.log(...) - Replace equality assertions like
?=with====
ScalaCheck
import org.scalacheck._
object StringSpecification extends Properties("String") {
property("startsWith") =
Prop.forAll { (a: String, b: String) =>
(a+b).startsWith(a)
}
}
Hedgehog
import hedgehog._
import hedgehog.runner._
object StringSpecification extends Properties {
override def tests: List[Test] = List(
property("startsWith", for {
a <- Gen.string(Gen.unicode, Range.linear(0, 100)).forAll
b <- Gen.string(Gen.unicode, Range.linear(0, 100)).forAll
} yield Result.assert((a+b).startsWith(a))
)
)
}
Gen
Some basic rules:
Gen.listandGen.listOfNcan be replaced with a call tolist(Range.linear(0, n))on a specificGeninstance.Gen.constis nowGen.constantArbitrary.arbitrary[Int]is nowGen.int(Range.linear(min, max))Gen.oneOfis nowGen.choice1
It's important to note that there are no more "default" Arbitrary instances
to summon. You must decided what kind of int or String you want to
generate, and what their Range is.
ScalaCheck
val genLeaf = Gen.const(Leaf)
val genNode = for {
v <- arbitrary[Int]
left <- genTree
right <- genTree
} yield Node(left, right, v)
def genTree: Gen[Tree] = Gen.oneOf(genLeaf, genNode)
Hedgehog
val genLeaf = Gen.constant(Leaf)
val genNode = for {
v <- Gen.int(Range.linear(Integer.MaxValue, Integer.MinValue))
left <- genTree
right <- genTree
} yield Node(left, right, v)
def genTree: Gen[Tree] = Gen.choice1(genLeaf, genNode)
Arbitrary
Some basic rules:
- Replace
implict deffunctions that returnArbitraryto a function that returns theGendirectly.
ScalaCheck
This example was taken from the ScalaCheck Guide.
implicit def arbTree[T](implicit a: Arbitrary[T]): Arbitrary[Tree[T]] = Arbitrary {
val genLeaf = for(e <- Arbitrary.arbitrary[T]) yield Leaf(e)
def genInternal(sz: Int): Gen[Tree[T]] = for {
n <- Gen.choose(sz/3, sz/2)
c <- Gen.listOfN(n, sizedTree(sz/2))
} yield Internal(c)
def sizedTree(sz: Int) =
if(sz <= 0) genLeaf
else Gen.frequency((1, genLeaf), (3, genInternal(sz)))
Gen.sized(sz => sizedTree(sz))
}
def genTree[T](g: Gen[T]): Gen[Tree[T]] = {
val genLeaf = for(e <- g) yield Leaf(e)
def genInternal(sz: Size): Gen[Tree[T]] = for {
n <- Gen.choose(sz.value/3, sz.value/2)
c <- sizedTree(sz.value/2).list(Range.linear(0, n))
} yield Internal(c)
def sizedTree(sz: Size) =
if(sz.value <= 0) genLeaf
else Gen.frequency1((1, genLeaf), (3, genInternal(sz)))
Gen.sized(sz => sizedTree(sz))
}
Shrink
This is assuming you're even writing them in the first place...
ScalaCheck
case class Data(a: String, i: Int)
implicit def arbData: Arbitrary[Data] =
Arbitrary[Data] {
for {
s <- arbitrary[String]
i <- arbitrary[Int]
} yield Data(a, i)
}
implicit def shrink: Shrink[Data] =
Shrink[Data] { case Data(a, i) =>
shrink(a).map(a2 => Data(a2, i)) append
shrink(i).map(i2 => Data(a, i2))
}
Hedgehog
Good news, you don't need to do anything! Just write your generators.
def genData: Gen[Data] =
for {
s <- Gen.string(Gen.unicode, Range.linear(0, 100))
i <- Gen.int(Range.linear(-100, 100))
} yield Data(a, i)