Permalink
Browse files

Scalacheck properties section (#2)

* Adds scalacheck-shapeless dependency

* Includes the properties section

* Adds the tests for the properties section

* Improves the documentation and examples

* Minor improvements on code doc

* Unifies the methods indentations
  • Loading branch information...
1 parent bf7642a commit cf9cf15ec585b346a48bfffe92f31101554b0336 @fedefernandez fedefernandez committed with raulraja Jul 6, 2016
View
@@ -15,6 +15,7 @@ lazy val scalacheck = (project in file("."))
"org.scala-exercises" %% "exercise-compiler" % version.value,
"org.scala-exercises" %% "definitions" % version.value,
"org.scalacheck" %% "scalacheck" % "1.12.5",
+ "com.github.alexarchambault" %% "scalacheck-shapeless_1.12" % "0.3.1",
compilerPlugin("org.spire-math" %% "kind-projector" % "0.7.1")
)
)
@@ -0,0 +1,19 @@
+package scalachecklib
+
+import org.scalacheck._
+
+object PropertiesHelpers {
+
+ class ZeroSpecification(value: Int) extends Properties("Zero") {
+
+ import Prop.{BooleanOperators, forAll}
+
+ property("addition property") = forAll { n: Int => (n != 0) ==> (n + value == n) }
+
+ property("additive inverse property") = forAll { n: Int => (n != 0) ==> (n + (-n) == value) }
+
+ property("multiplication property") = forAll { n: Int => (n != 0) ==> (n * value == 0) }
+
+ }
+
+}
@@ -0,0 +1,176 @@
+package scalachecklib
+
+import org.scalatest._
+import org.scalatest.prop.Checkers
+import PropertiesHelpers._
+
+/** A ''property'' is the testable unit in ScalaCheck, and is represented by the `org.scalacheck.Prop` class.
+ * There are several ways to create properties in ScalaCheck, one of them is to use the `org.scalacheck.Prop.forAll`
+ * method like in the example below.
+ *
+ * {{{
+ * val propSqrt = forAll { (n: Int) => scala.math.sqrt(n*n) == n }
+ * }}}
+ *
+ * That method creates universally quantified properties directly, but it is also
+ * possible to create new properties by combining other properties, or to use any of the specialised
+ * methods in the `org.scalacheck.Prop` object.
+ *
+ * @param name properties
+ */
+object PropertiesSection extends Checkers with Matchers with exercise.Section {
+
+ /** ==Universally quantified properties==
+ *
+ * As mentioned before, `org.scalacheck.Prop.forAll` creates universally quantified properties.
+ * `forAll` takes a function as parameter, and creates a property out of it that can be tested with the `check`
+ * method or with Scalatest, like in these examples.
+ *
+ *
+ * The function passed to `forAll` should return `Boolean` or another property, and can take parameters of any types,
+ * as long as there exist implicit `Arbitrary` instances for the types.
+ * By default, ScalaCheck has instances for common types like `Int`, `String`, `List`, etc, but it is also possible
+ * to define your own `Arbitrary` instances.
+ *
+ * For example:
+ */
+ def universallyQuantifiedPropertiesString(res0: Boolean) = {
+
+ import org.scalacheck.Prop.forAll
+
+ check {
+ forAll { (s1: String, s2: String) =>
+ (s1 + s2).endsWith(s2) == res0
+ }
+ }
+
+ }
+
+ /** When you run `check` on the properties, ScalaCheck generates random instances of the function parameters and
+ * evaluates the results, reporting any failing cases.
+ *
+ * You can also give `forAll` a specific data generator. In the following example `smallInteger` defines a generator
+ * that generates integers between `0` and `100`, inclusively.
+ *
+ * This way of using the `forAll` method is good to use when you want to control the data generation by specifying
+ * exactly which generator that should be used, and not rely on a default generator for the given type.
+ */
+ def universallyQuantifiedPropertiesGen(res0: Boolean) = {
+
+ import org.scalacheck.Gen
+ import org.scalacheck.Prop.forAll
+
+ val smallInteger = Gen.choose(0,100)
+
+ check {
+ forAll(smallInteger) { n =>
+ (n >= 0 && n <= 100) == res0
+ }
+ }
+
+ }
+
+ /** ==Conditional properties==
+ *
+ * Sometimes, a specification takes the form of an implication. In ScalaCheck, you can use the implication
+ * operator `==>` to filter the generated values.
+ *
+ * If the implication operator is given a condition that is hard or impossible to fulfill, ScalaCheck might
+ * not find enough passing test cases to state that the property holds. In the following trivial example,
+ * all cases where `n` is non-zero will be thrown away:
+ *
+ * {{{
+ * scala> import org.scalacheck.Prop.{forAll, BooleanOperators}
+ * scala> val propTrivial = forAll { n: Int =>
+ * | (n == 0) ==> (n == 0)
+ * | }
+ *
+ * scala> propTrivial.check
+ * ! Gave up after only 4 passed tests. 500 tests were discarded.
+ * }}}
+ *
+ * It is possible to tell ScalaCheck to try harder when it generates test cases, but generally you should
+ * try to refactor your property specification instead of generating more test cases, if you get this scenario.
+ *
+ * Using implications, we realise that a property might not just pass or fail, it could also be undecided if
+ * the implication condition doesn't get fulfilled.
+ *
+ * In this example, ScalaCheck will only care for the cases when `n` is an even number.
+ */
+ def conditionalProperties(res0: Int) = {
+
+ import org.scalacheck.Prop.{BooleanOperators, forAll}
+
+ check {
+ forAll { n: Int =>
+ (n % 2 == 0) ==> (n % 2 == res0)
+ }
+ }
+
+ }
+
+ /** ==Combining Properties==
+ *
+ * A third way of creating properties, is to combine existing properties into new ones.
+ *
+ * {{{
+ * val p1 = forAll(...)
+ * val p2 = forAll(...)
+ * val p3 = p1 && p2
+ * val p4 = p1 || p2
+ * val p5 = p1 == p2
+ * val p6 = all(p1, p2) // same as p1 && p2
+ * val p7 = atLeastOne(p1, p2) // same as p1 || p2
+ * }}}
+ *
+ * Here, `p3` will hold if and only if both `p1` and `p2` hold, `p4` will hold if either `p1` or `p2` holds,
+ * and `p5` will hold if `p1` holds exactly when `p2` holds and vice versa.
+ */
+ def combiningProperties(res0: Boolean, res1: Boolean) = {
+
+ import org.scalacheck.Gen
+ import org.scalacheck.Prop.forAll
+
+ val smallInteger = Gen.choose(0,100)
+
+ check {
+ forAll(smallInteger) { n => (n > 100) == res0 } &&
+ forAll(smallInteger) { n => (n >= 0) == res1 }
+ }
+
+ }
+
+ /** ==Grouping Properties==
+ *
+ * Often you want to specify several related properties, perhaps for all methods in a class.
+ * ScalaCheck provides a simple way of doing this, through the `Properties` trait.
+ * Look at the following specifications that define some properties for zero:
+ *
+ * {{{
+ * import org.scalacheck._
+ *
+ * class ZeroSpecification(value: Int) extends Properties("Zero") {
+ *
+ * import org.scalacheck.Prop.{BooleanOperators, forAll}
+ *
+ * property("addition property") = forAll { n: Int => (n != 0) ==> (n + value == n) }
+ *
+ * property("additive inverse property") = forAll { n: Int => (n != 0) ==> (n + (-n) == value) }
+ *
+ * property("multiplication property") = forAll { n: Int => (n != 0) ==> (n * value == 0) }
+ *
+ * }
+ * }}}
+ *
+ * You can use the check method of the `Properties` class to check all specified properties,
+ * just like for simple `Prop` instances. In fact, `Properties` is a subtype of `Prop`,
+ * so you can use it just as if it was a single property.
+ *
+ * That single property holds if and only if all of the contained properties hold.
+ */
+ def groupingProperties(res0: Int) = {
+
+ check(new ZeroSpecification(res0))
+
+ }
+}
@@ -0,0 +1,16 @@
+package scalachecklib
+
+/** ScalaCheck is a tool for testing Scala and Java programs, based on property specifications and automatic test data generation.
+ *
+ * @param name scalacheck
+ */
+object ScalacheckLibrary extends exercise.Library {
+ override def owner = "scala-exercises"
+ override def repository = "exercises-scalacheck"
+
+ override def color = Some("#5B5988")
+
+ override def sections = List(
+ PropertiesSection
+ )
+}
@@ -0,0 +1,63 @@
+package exercises.scalachecklib
+
+import org.scalacheck.Shapeless._
+import org.scalatest.Spec
+import org.scalatest.prop.Checkers
+import shapeless.HNil
+
+import scalachecklib.PropertiesSection
+
+
+class PropertiesSpec extends Spec with Checkers {
+
+ def `always ends with the second string` = {
+
+ check(
+ Test.testSuccess(
+ PropertiesSection.universallyQuantifiedPropertiesString _,
+ true :: HNil
+ )
+ )
+ }
+
+ def `all numbers are generated between the desired interval` = {
+
+ check(
+ Test.testSuccess(
+ PropertiesSection.universallyQuantifiedPropertiesGen _,
+ true :: HNil
+ )
+ )
+ }
+
+ def `all generated numbers are even` = {
+
+ check(
+ Test.testSuccess(
+ PropertiesSection.conditionalProperties _,
+ 0 :: HNil
+ )
+ )
+ }
+
+ def `only the second condition is true` = {
+
+ check(
+ Test.testSuccess(
+ PropertiesSection.combiningProperties _,
+ false :: true :: HNil
+ )
+ )
+ }
+
+ def `the zero specification only works for 0` = {
+
+ check(
+ Test.testSuccess(
+ PropertiesSection.groupingProperties _,
+ 0 :: HNil
+ )
+ )
+ }
+
+}
@@ -0,0 +1,54 @@
+package exercises.scalachecklib
+
+import cats.data.Xor
+
+import shapeless._
+import shapeless.ops.function._
+
+import org.scalacheck.{Prop, Arbitrary }
+import org.scalacheck.Gen
+import org.scalacheck.Shapeless._
+import org.scalatest.exceptions._
+
+import Prop.forAll
+
+object Test {
+
+ def testSuccess[F, R, L <: HList](method: F, answer: L)(
+ implicit
+ A: Arbitrary[L],
+ fntop: FnToProduct.Aux[F, L R]
+ ): Prop = {
+ val rightGen = genRightAnswer(answer)
+ val rightProp = forAll(rightGen)({ p
+
+ val result = Xor.catchOnly[GeneratorDrivenPropertyCheckFailedException]({ fntop(method)(p) })
+ result match {
+ case Xor.Left(exc) exc.cause match {
+ case Some(originalException) throw originalException
+ case _ false
+ }
+ case _ true
+ }
+ })
+
+ val wrongGen = genWrongAnswer(answer)
+ val wrongProp = forAll(wrongGen)({ p
+ Xor.catchNonFatal({ fntop(method)(p) }).isLeft
+ })
+
+ Prop.all(rightProp, wrongProp)
+ }
+
+ def genRightAnswer[L <: HList](answer: L): Gen[L] = {
+ Gen.const(answer)
+ }
+
+ def genWrongAnswer[L <: HList](l: L)(
+ implicit
+ A: Arbitrary[L]
+ ): Gen[L] = {
+ A.arbitrary.suchThat(_ != l)
+ }
+}
+

0 comments on commit cf9cf15

Please sign in to comment.