Last active
November 28, 2017 21:54
-
-
Save dcastro/b01f9c441c79a4f644705268dcbc3750 to your computer and use it in GitHub Desktop.
Scalacheck demo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* useful links | |
* | |
* https://github.com/rickynils/scalacheck/blob/master/doc/UserGuide.md | |
* http://www.scalatest.org/user_guide/generator_driven_property_checks | |
* | |
*/ | |
import org.scalacheck._ | |
import org.scalacheck.Arbitrary._ | |
import org.scalacheck.Prop._ | |
val propReverse = forAll { s: String => | |
s.reverse.reverse == s | |
} | |
propReverse.check | |
object SortProperties extends Properties("Sort") { | |
property("sorting is idempotent") = forAll { xs: List[Int] => | |
xs.sorted.sorted == xs.sorted | |
} | |
property("sorting doesn't remove elements") = forAll { xs: List[Int] => | |
xs.forall { x => | |
xs.sorted.contains(x) | |
} | |
} | |
property("sorting doesn't add new elements") = forAll { xs: List[Int] => | |
xs.sorted.forall {x => | |
xs.contains(x) | |
} | |
} | |
property("sorting doesn't change element count") = forAll { xs: List[Int] => | |
xs.length == xs.sorted.length | |
} | |
property("each elem is smaller than or equal to the next") = forAll { xs: List[Int] => | |
// this property doesn't make sense for lists with less than 2 elements | |
(xs.length >= 2) ==> { | |
xs.sorted.sliding(2).forall { | |
case List(elem, next) => elem <= next | |
} | |
} | |
} | |
} | |
SortProperties.check() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import cats.implicits._ | |
import org.scalacheck._ | |
import org.scalacheck.Arbitrary._ | |
import org.scalacheck.Prop._ | |
case class Person(name: String, age: Int) | |
object Person { | |
val genPerson: Gen[Person] = | |
for { | |
name <- Gen.alphaNumStr | |
age <- Gen.posNum[Int] | |
} yield Person(name, age) | |
val genYoungPerson: Gen[Person] = | |
for { | |
person <- genPerson | |
age <- Gen.chooseNum(0, 18) | |
} yield person.copy(age = age) | |
val genOldPerson: Gen[Person] = | |
for { | |
person <- genPerson | |
age <- Gen.chooseNum(18, Int.MaxValue) | |
} yield person.copy(age = age) | |
val genYoungOrOldPerson: Gen[Person] = | |
Gen.frequency( | |
1 -> genYoungPerson, | |
2 -> genOldPerson | |
) | |
// canonical random Person | |
implicit val arbPerson: Arbitrary[Person] = Arbitrary(genPerson) | |
} | |
import Person._ | |
genYoungPerson.sample | |
arbitrary[Person].sample | |
object App { | |
def oldest(people: List[Person]): Person = | |
// this is going to fail for empty lists | |
people.maxBy(_.age) | |
val p = forAll { people: List[Person] => | |
val oldest: Person = App.oldest(people) | |
// the oldest person's age is greater than everyone else's age | |
people.forall { p => | |
oldest.age >= p.age | |
} | |
} | |
} | |
App.p.check | |
// fixed version of App | |
object App2 { | |
def oldest(people: List[Person]): Option[Person] = people match { | |
case Nil => None | |
case _ => people.maxBy(_.age).some | |
} | |
val p = forAll { people: List[Person] => | |
val oldest: Option[Person] = App2.oldest(people) | |
// the oldest person's age is greater than everyone else's age | |
people match { | |
case Nil => oldest.isEmpty | |
case _ => people.forall { p => | |
oldest match { | |
case Some(o) => o.age >= p.age | |
case _ => false | |
} | |
} | |
} | |
} | |
} | |
App2.p.check | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.scalacheck._ | |
import org.scalacheck.Arbitrary._ | |
import spray.json._ | |
object JsGens { | |
val genJsString = Gen.alphaNumStr.map(s => JsString(s)) | |
val genJsNum = arbitrary[BigDecimal] map JsNumber.apply | |
val genJsBool = arbitrary[Boolean] map JsBoolean.apply | |
val genJsNull = Gen.const(JsNull) | |
val genKeyValuePair: Gen[(String, JsValue)] = | |
for { | |
key <- Gen.alphaNumStr | |
value <- arbitrary[JsValue] | |
} yield (key, value) | |
val genJsObject: Gen[JsObject] = for { | |
n <- Gen.chooseNum[Int](0, 10) | |
pairs <- Gen.listOfN(n, genKeyValuePair) | |
} yield JsObject(pairs: _*) | |
val genJsArray: Gen[JsArray] = for { | |
n <- Gen.chooseNum(0, 10) | |
values <- Gen.listOfN(n, genJsValue) | |
} yield JsArray(values: _*) | |
def genJsValue: Gen[JsValue] = | |
Gen.frequency( | |
2 -> genJsString, | |
2 -> genJsBool, | |
2 -> genJsNum, | |
1 -> genJsNull, | |
2 -> genJsObject, | |
2 -> genJsArray | |
) | |
implicit def arbJsObject: Arbitrary[JsObject] = Arbitrary(genJsObject) | |
implicit def arbJsValue: Arbitrary[JsValue] = Arbitrary(genJsValue) | |
} | |
import JsGens._ | |
arbitrary[JsObject].sample.map(_.prettyPrint) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment