Skip to content

Commit 3b148d5

Browse files
author
Fede Fernández
authored
Scalacheck generators (#4)
* 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 * Adds the new Generators section * Adds generators section test * Adds the sized generator exercise and test * Add snapshot plugin resolver * Add snapshot version for scala-exercises * Use new definitions package structure * Use new definitions package structure * Changes the section headers in the Generators section
1 parent 52acf33 commit 3b148d5

File tree

4 files changed

+318
-1
lines changed

4 files changed

+318
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package scalachecklib
2+
3+
object GeneratorsHelper {
4+
5+
case class Foo(intValue: Int, charValue: Char)
6+
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
package scalachecklib
2+
3+
import org.scalatest.Matchers
4+
import org.scalatest.prop.Checkers
5+
6+
/** Generators are responsible for generating test data in ScalaCheck, and are represented by the `org.scalacheck.Gen`
7+
* class. In the `Gen` object, there are several methods for creating new and modifying existing generators.
8+
* We will show how to use some of them in this section. For a more complete reference of what is available,
9+
* please see the [[https://www.scalacheck.org/files/scalacheck_2.11-1.12.5-api/index.html API scaladoc]].
10+
*
11+
*
12+
* A generator can be seen simply as a function that takes some generation parameters, and (maybe) returns a
13+
* generated value. That is, the type `Gen[T]` may be thought of as a function of type `Gen.Params => Option[T]`.
14+
* However, the Gen class contains additional methods to make it possible to map generators, use them in
15+
* for-comprehensions and so on. Conceptually, though, you should think of generators simply as functions, and the
16+
* combinators in the `Gen` object can be used to create or modify the behaviour of such generator functions.
17+
*
18+
* @param name generators
19+
*/
20+
object GeneratorsSection extends Checkers with Matchers with org.scalaexercises.definitions.Section {
21+
22+
import GeneratorsHelper._
23+
24+
/** Lets see how to create a new generator. The best way to do it is to use the generator combinators that exist
25+
* in the `org.scalacheck.Gen` module. These can be combined using a for-comprehension. Suppose you need a generator
26+
* which generates a tuple that contains two random integer values, one of them being at least twice as big as the
27+
* other. The following definition does this:
28+
*/
29+
def forComprehension(res0: Boolean) = {
30+
31+
import org.scalacheck.Gen
32+
import org.scalacheck.Prop.forAll
33+
34+
val myGen = for {
35+
n <- Gen.choose(10, 20)
36+
m <- Gen.choose(2 * n, 500)
37+
} yield (n,m)
38+
39+
check {
40+
forAll(myGen) {
41+
case (n, m) => (m >= 2 * n) == res0
42+
}
43+
}
44+
45+
}
46+
47+
/** You can create generators that picks one value out of a selection of values.
48+
* The `oneOf` method creates a generator that randomly picks one of its parameters each time it generates a value.
49+
* Notice that plain values are implicitly converted to generators (which always generates that value) if needed.
50+
*
51+
*
52+
* The following generator generates a vowel:
53+
*/
54+
def genOf(res0: Seq[Char]) = {
55+
56+
import org.scalacheck.Gen
57+
import org.scalacheck.Prop.forAll
58+
59+
val vowel = Gen.oneOf('A', 'E', 'I', 'O', 'U')
60+
61+
val validChars: Seq[Char] = res0
62+
63+
check(forAll(vowel) { v =>
64+
validChars.contains(v)
65+
})
66+
}
67+
68+
/** The distribution is uniform, but if you want to control it you can use the frequency combinator:
69+
*
70+
* {{{
71+
* val vowel = Gen.frequency(
72+
* (3, 'A'),
73+
* (4, 'E'),
74+
* (2, 'I'),
75+
* (3, 'O'),
76+
* (1, 'U')
77+
* )
78+
* }}}
79+
*
80+
* Now, the vowel generator will generate ''E:s'' more often than ''U:s''. Roughly, 4/14 of the values generated
81+
* will be ''E:s'', and 1/14 of them will be ''U:s''.
82+
*
83+
* Another methods in the `Gen` API:
84+
* {{{
85+
* def alphaChar: Gen[Char]
86+
*
87+
* def alphaStr: Gen[String]
88+
*
89+
* def posNum[T](implicit n: Numeric[T]): Gen[T]
90+
*
91+
* def listOf[T](g: Gen[T]): Gen[List[T]]
92+
*
93+
* def listOfN[T](n: Int, g: Gen[T]): Gen[List[T]]
94+
* }}}
95+
*/
96+
def genAPI(res0: Boolean, res1: Boolean, res2: Int) = {
97+
98+
import org.scalacheck.Gen.{alphaChar, posNum, listOfN}
99+
import org.scalacheck.Prop.forAll
100+
101+
check(forAll(alphaChar)(_.isDigit == res0))
102+
103+
check(forAll(posNum[Int])(n => (n > 0) == res1))
104+
105+
check {
106+
forAll(listOfN(10, posNum[Int])) { list =>
107+
!list.exists(_ < 0) && list.length == res2
108+
}
109+
}
110+
}
111+
112+
/** ==Conditional Generators==
113+
*
114+
* Conditional generators can be defined using `Gen.suchThat`.
115+
*
116+
* Conditional generators works just like conditional properties, in the sense that if the condition is too hard,
117+
* ScalaCheck might not be able to generate enough values, and it might report a property test as undecided.
118+
* The `smallEvenInteger` definition is probably OK, since it will only throw away half of the generated numbers,
119+
* but one has to be careful when using the `suchThat` operator.
120+
*/
121+
def conditionalOperators(res0: Int) = {
122+
123+
import org.scalacheck.Gen
124+
import org.scalacheck.Prop.forAll
125+
126+
val smallEvenInteger = Gen.choose(0,200) suchThat (_ % 2 == 0)
127+
128+
check(forAll(smallEvenInteger)(_ % 2 == res0))
129+
}
130+
131+
/** ==Case class Generators==
132+
*
133+
* On the basis of the above we can create a generator for the next case class:
134+
*
135+
* {{{
136+
* case class Foo(intValue: Int, charValue: Char)
137+
* }}}
138+
*/
139+
def caseClassGenerator(res0: Boolean) = {
140+
141+
import org.scalacheck.Gen
142+
import org.scalacheck.Prop.forAll
143+
144+
val fooGen = for {
145+
intValue <- Gen.posNum[Int]
146+
charValue <- Gen.alphaChar
147+
} yield Foo(intValue, charValue)
148+
149+
check(forAll(fooGen) {
150+
foo => foo.intValue > 0 && foo.charValue.isDigit == res0
151+
})
152+
}
153+
154+
/** ==Sized Generators==
155+
*
156+
* When ScalaCheck uses a generator to generate a value, it feeds it with some parameters. One of the parameters
157+
* the generator is given, is a size value, which some generators use to generate their values.
158+
*
159+
* If you want to use the size parameter in your own generator, you can use the `Gen.sized` method:
160+
*
161+
* {{{
162+
* def sized[T](f: Int => Gen[T])
163+
* }}}
164+
*
165+
* In this example we're creating a generator that produces two lists of numbers where 1/3 are positive and 2/3 are
166+
* negative. ''Note: We're also returning the original size to verify the behaviour''
167+
*/
168+
def sizedGenerator(res0: Int, res1: Int) = {
169+
170+
import org.scalacheck.Gen
171+
import org.scalacheck.Prop.forAll
172+
173+
val myGen = Gen.sized { size =>
174+
val positiveNumbers = size / 3
175+
val negativeNumbers = size * 2 / 3
176+
for {
177+
posNumList <- Gen.listOfN(positiveNumbers, Gen.posNum[Int])
178+
negNumList <- Gen.listOfN(negativeNumbers, Gen.posNum[Int] map (n => -n))
179+
} yield (size, posNumList, negNumList)
180+
}
181+
182+
check(forAll(myGen) {
183+
case (genSize, posN, negN) =>
184+
posN.length == genSize / res0 && negN.length == genSize * res1 / 3
185+
})
186+
}
187+
188+
/** ==Generating Containers==
189+
*
190+
* There is a special generator, `Gen.containerOf`, that generates containers such as lists and arrays.
191+
* They take another generator as argument, that is responsible for generating the individual items.
192+
* You can use it in the following way:
193+
*
194+
* {{{
195+
* val genIntList = Gen.containerOf[List,Int](Gen.oneOf(1, 3, 5))
196+
*
197+
* val genStringStream = Gen.containerOf[Stream,String](Gen.alphaStr)
198+
*
199+
* val genBoolArray = Gen.containerOf[Array,Boolean](true)
200+
* }}}
201+
*
202+
* By default, ScalaCheck supports generation of `List`, `Stream`, `Set`, `Array`, and `ArrayList`
203+
* (from `java.util`). You can add support for additional containers by adding implicit `Buildable` instances.
204+
*
205+
* There is also `Gen.nonEmptyContainerOf` for generating non-empty containers, and `Gen.containerOfN` for
206+
* generating containers of a given size.
207+
*/
208+
def generatingContainers(res0: List[Int]) = {
209+
210+
import org.scalacheck.Gen
211+
import org.scalacheck.Prop.forAll
212+
213+
val genIntList = Gen.containerOf[List,Int](Gen.oneOf(2, 4, 6))
214+
215+
val validNumbers: List[Int] = res0
216+
217+
check(forAll(genIntList)(_ forall (elem => validNumbers.contains(elem))))
218+
}
219+
220+
}

src/main/scala/scalachecklib/ScalacheckLibrary.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ object ScalacheckLibrary extends org.scalaexercises.definitions.Library {
1111
override def color = Some("#5B5988")
1212

1313
override def sections = List(
14-
PropertiesSection
14+
PropertiesSection,
15+
GeneratorsSection
1516
)
1617
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package exercises.scalachecklib
2+
3+
import org.scalacheck.Shapeless._
4+
import org.scalatest.Spec
5+
import org.scalatest.prop.Checkers
6+
import shapeless.HNil
7+
8+
import scalachecklib.GeneratorsSection
9+
10+
class GeneratorsSpec extends Spec with Checkers {
11+
12+
def `for-comprehension generator` = {
13+
14+
check(
15+
Test.testSuccess(
16+
GeneratorsSection.forComprehension _,
17+
true :: HNil
18+
)
19+
)
20+
21+
}
22+
23+
def `oneOf method` = {
24+
25+
check(
26+
Test.testSuccess(
27+
GeneratorsSection.genOf _,
28+
Seq('A', 'E', 'I', 'O', 'U') :: HNil
29+
)
30+
)
31+
32+
}
33+
34+
def `alphaChar, posNum and listOfN` = {
35+
36+
check(
37+
Test.testSuccess(
38+
GeneratorsSection.genAPI _,
39+
false :: true :: 10 :: HNil
40+
)
41+
)
42+
43+
}
44+
45+
def `suchThat condition` = {
46+
47+
check(
48+
Test.testSuccess(
49+
GeneratorsSection.conditionalOperators _,
50+
0 :: HNil
51+
)
52+
)
53+
54+
}
55+
56+
def `case class generator` = {
57+
58+
check(
59+
Test.testSuccess(
60+
GeneratorsSection.caseClassGenerator _,
61+
false :: HNil
62+
)
63+
)
64+
65+
}
66+
67+
def `sized generator` = {
68+
69+
check(
70+
Test.testSuccess(
71+
GeneratorsSection.sizedGenerator _,
72+
3 :: 2 :: HNil
73+
)
74+
)
75+
76+
}
77+
78+
def `list container` = {
79+
80+
check(
81+
Test.testSuccess(
82+
GeneratorsSection.generatingContainers _,
83+
List(2, 4, 6) :: HNil
84+
)
85+
)
86+
87+
}
88+
89+
}

0 commit comments

Comments
 (0)