propr 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. data/NOTES.md +62 -0
  2. data/README.md +553 -0
  3. data/Rakefile +83 -0
  4. data/TODO.md +64 -0
  5. data/lib/propr.rb +123 -0
  6. data/lib/propr/dsl.rb +6 -0
  7. data/lib/propr/dsl/check.rb +49 -0
  8. data/lib/propr/dsl/property.rb +62 -0
  9. data/lib/propr/property.rb +23 -0
  10. data/lib/propr/random.rb +143 -0
  11. data/lib/propr/random/array.rb +19 -0
  12. data/lib/propr/random/bigdecimal.rb +43 -0
  13. data/lib/propr/random/boolean.rb +7 -0
  14. data/lib/propr/random/complex.rb +0 -0
  15. data/lib/propr/random/date.rb +17 -0
  16. data/lib/propr/random/float.rb +60 -0
  17. data/lib/propr/random/hash.rb +55 -0
  18. data/lib/propr/random/integer.rb +38 -0
  19. data/lib/propr/random/maybe.rb +0 -0
  20. data/lib/propr/random/nil.rb +8 -0
  21. data/lib/propr/random/range.rb +32 -0
  22. data/lib/propr/random/rational.rb +0 -0
  23. data/lib/propr/random/set.rb +22 -0
  24. data/lib/propr/random/string.rb +41 -0
  25. data/lib/propr/random/symbol.rb +13 -0
  26. data/lib/propr/random/time.rb +14 -0
  27. data/lib/propr/rspec.rb +97 -0
  28. data/lib/propr/runner.rb +53 -0
  29. data/lib/propr/shrink/array.rb +16 -0
  30. data/lib/propr/shrink/bigdecimal.rb +17 -0
  31. data/lib/propr/shrink/boolean.rb +11 -0
  32. data/lib/propr/shrink/complex.rb +0 -0
  33. data/lib/propr/shrink/date.rb +12 -0
  34. data/lib/propr/shrink/float.rb +17 -0
  35. data/lib/propr/shrink/hash.rb +18 -0
  36. data/lib/propr/shrink/integer.rb +10 -0
  37. data/lib/propr/shrink/maybe.rb +11 -0
  38. data/lib/propr/shrink/nil.rb +5 -0
  39. data/lib/propr/shrink/object.rb +5 -0
  40. data/lib/propr/shrink/range.rb +4 -0
  41. data/lib/propr/shrink/rational.rb +4 -0
  42. data/lib/propr/shrink/set.rb +18 -0
  43. data/lib/propr/shrink/string.rb +19 -0
  44. data/lib/propr/shrink/symbol.rb +5 -0
  45. data/lib/propr/shrink/time.rb +9 -0
  46. data/spec/examples/choose/array.example +12 -0
  47. data/spec/examples/choose/hash.example +12 -0
  48. data/spec/examples/choose/range.example +13 -0
  49. data/spec/examples/choose/set.example +12 -0
  50. data/spec/examples/guard.example +38 -0
  51. data/spec/examples/random/array.example +38 -0
  52. data/spec/examples/random/hash.example +18 -0
  53. data/spec/examples/random/integer.example +23 -0
  54. data/spec/examples/random/range.example +43 -0
  55. data/spec/examples/scale.example +17 -0
  56. data/spec/examples/shrink/array.example +20 -0
  57. data/spec/examples/shrink/bigdecimal.example +20 -0
  58. data/spec/examples/shrink/float.example +20 -0
  59. data/spec/examples/shrink/hash.example +20 -0
  60. data/spec/examples/shrink/integer.example +21 -0
  61. data/spec/examples/shrink/maybe.example +24 -0
  62. data/spec/examples/shrink/set.example +21 -0
  63. data/spec/examples/shrink/string.example +17 -0
  64. data/spec/issues/003.example +9 -0
  65. data/spec/spec_helper.rb +24 -0
  66. metadata +143 -0
@@ -0,0 +1,62 @@
1
+ ## Property DSL
2
+
3
+ Properties are basically just functions. They look like this:
4
+
5
+ lambda{|a,...| ... }
6
+
7
+ ## Property DSL
8
+
9
+ * `error?(ex) { ... }`
10
+ True if code block throws an exception
11
+
12
+ * `guard(cond)`
13
+ Skip test for inputs that don't meet the condition
14
+
15
+ * `label(str)`
16
+ Classify each invocation of the property
17
+
18
+ ## Random DSL
19
+
20
+ * `scale(n, zero)`
21
+ Scale the numeric value closer to zero, using current factor 0..1
22
+
23
+ * `guard(cond)`
24
+ Supress the generated value unless a condition is met
25
+
26
+ * `rand(limit)`
27
+ Generate a random number using `Kernel.rand`
28
+
29
+ ## Wiring
30
+
31
+ Define boolean property
32
+
33
+ >> f = lambda{|a,b,c| a + (b + c) == (a + b) + c }
34
+ => #<Proc:...>
35
+
36
+ >> p = Propr::Property.new("assoc", Propr::Dsl::Property.new(f))
37
+ => #<Propr::Property:...>
38
+
39
+ Define generator of random data
40
+
41
+ >> c = lambda do
42
+ bind(Integer.random) do |a|
43
+ bind(Integer.random) do |b|
44
+ bind(Integer.random) do |c|
45
+ unit([a, b, c]);
46
+ end; end; end; end
47
+ => #<Proc:...>
48
+
49
+ >> c = Propr::Dsl::Check.wrap(r)
50
+ => #<Proc:...>
51
+
52
+ Generating a random input
53
+
54
+ >> Propr::Random.eval r
55
+ => [-1473273057635678493, 3003717222078111739, -4075345202237457298]
56
+
57
+ Simple API for testing the property with random data
58
+
59
+ >> p.check { Propr::Random.eval c }
60
+ => true
61
+
62
+
@@ -0,0 +1,553 @@
1
+ ## Introduction
2
+
3
+ The usual approach to testing software is to describe a set of test inputs
4
+ and their corresponding expected outputs. The program is run with these
5
+ inputs and the actual outputs are compared with the expected outputs to
6
+ ensure the program behaves as expected. This methodology is simple to
7
+ implement and automate, but suffers from problems like:
8
+
9
+ * Writing test cases is tedious.
10
+ * Non-obvious edge cases aren't tested.
11
+ * Code coverage tools alone don't provide much assurance.
12
+
13
+ Property-based testing is an alternative, and complementary, approach in
14
+ which the general relationships between program inputs and desired output
15
+ are expressed, rather than enumerating particular inputs and outputs. The
16
+ properties specify things like, "assuming the program is correct, when its
17
+ run with any valid inputs, the inputs and the program output are related by
18
+ `f(input, output)`". The test framework produces random (valid) inputs,
19
+ searching for a counterexample.
20
+
21
+ ## Installation
22
+
23
+ There are a few things I'd like to fix before publishing this as a gem. Until
24
+ then, you can install directly from the git repo using Bundler, with this in
25
+ your Gemfile:
26
+
27
+ gem "propr", git: "git@github.com:kputnam/propr.git", branch: "rewrite"
28
+
29
+ You'll probably want to specify the current tag, also (eg, `..., tag: "v0.1.0"`)
30
+
31
+ ## Properties
32
+
33
+ The following example demonstrates testing a property with a specific input,
34
+ then generalizing the test for any input.
35
+
36
+ ```ruby
37
+ describe Array do
38
+ include Propr::RSpec
39
+
40
+ describe "#+" do
41
+ # Traditional unit test
42
+ it "sums lengths" do
43
+ xs = [100, 200, 300]
44
+ ys = [400, 500]
45
+ (xs + ys).length.should == xs.length + ys.length
46
+ end
47
+
48
+ # Property-based test
49
+ property("sums lengths"){|xs, ys| (xs + ys).length == xs.length + ys.length }
50
+ .check([100, 200, 300], [500, 200])
51
+ .check{ sequence [Array.random { Integer.random }, Array.random { Integer.random }] }
52
+ end
53
+ end
54
+ ```
55
+
56
+ The following example is similar, but contains an error in specification
57
+
58
+ ```ruby
59
+ describe Array do
60
+ include Propr::RSpec
61
+
62
+ describe "#|" do
63
+ # Traditional unit test
64
+ it "sums lengths" do
65
+ xs = [100, 200, 300]
66
+ ys = [400, 500]
67
+ (xs | ys).length.should == xs.length + ys.length
68
+ end
69
+
70
+ # Property-based test
71
+ property("sums lengths"){|xs, ys| (xs | ys).length == xs.length + ys.length }
72
+ .check([100, 200, 300], [400, 500])
73
+ .check{ sequence [Array.random{Integer.random(min:0, max:50)}]*2 }
74
+ end
75
+ end
76
+ ```
77
+
78
+ When this specification is executed, the following error is reported.
79
+
80
+ $ rake spec
81
+ ..F
82
+
83
+ Failures:
84
+
85
+ 1) Array#| sums lengths
86
+ Failure/Error: raise Falsifiable.new(counterex, m.shrink(counterex), passed, skipped)
87
+ Propr::Falsifiable:
88
+ input: [25, 24], [24, 27]
89
+ shrunken: [], [0, 0]
90
+ after: 49 passed, 0 skipped
91
+ # ./lib/propr/rspec.rb:29:in `block in check'
92
+
93
+ Finished in 0.22829 seconds
94
+ 3 examples, 1 failure
95
+
96
+ You may have figured out the error is that `|` removes duplicate elements
97
+ from the result. We might not have caught the mistake by writing individual
98
+ test cases. The output indicates Propr generated 49 sets of input before
99
+ finding one that failed.
100
+
101
+ Now that a failing test case has been identified, you might write another
102
+ `check` with those specific inputs to prevent regressions.
103
+
104
+ You could also print the initial random seed like this and when a test fails,
105
+ explicitly set the random seed to regenerate the same inputs for the entire
106
+ test suite:
107
+
108
+ $ cat spec/spec_helper.rb
109
+ RSpec.configure do |config|
110
+ srand.tap{|seed| puts "Random seed is #{seed}"; srand seed }
111
+ end
112
+
113
+ $ rake spec
114
+ Random seed is 146211424375622429406889408197139382303
115
+ ..F
116
+
117
+ Failures:
118
+
119
+ 1) Array#| sums lengths
120
+ Failure/Error: raise Falsifiable.new(counterex, m.shrink(counterex), passed, skipped)
121
+ Propr::Falsifiable:
122
+ input: [25, 24], [24, 27]
123
+ shrunken: [], [0, 0]
124
+ after: 49 passed, 0 skipped
125
+
126
+ Finished in 0.22829 seconds
127
+ 3 examples, 1 failure
128
+
129
+ Now change spec\_helper.rb to explicitly set the random seed:
130
+
131
+ $ cat spec/spec_helper.rb
132
+ RSpec.configure do |config|
133
+ srand 146211424375622429406889408197139382303
134
+ srand.tap{|seed| puts "Random seed is #{seed}"; srand seed }
135
+ end
136
+
137
+ $ rake spec
138
+ Random seed is 146211424375622429406889408197139382303
139
+
140
+ The remaining output should be identical every time you run the suite, so
141
+ long as specs are in the same order each time.
142
+
143
+ ### Just Plain Functions
144
+
145
+ Properties are basically just functions, they should return `true` or `false`.
146
+
147
+ p = Propr::Property.new("name", lambda{|a,b| a + b == b + a })
148
+
149
+ You can invoke a property using `#check`. Like lambdas and procs, you can also
150
+ invoke them using `#call` or `#[]`.
151
+
152
+ p.check(3, 4) #=> true
153
+ p.check("x", "y") #=> false
154
+
155
+ But you can also invoke them by yielding a function that generates random inputs.
156
+
157
+ m = Propr::Random
158
+ p.check { m.eval(m.sequence [Integer.random, Float.random]) } #=> true
159
+ p.check { m.eval(m.sequence [String.random , String.random]) } #=> false
160
+
161
+ When invoked with a block, `check` will run `p` with 100 random inputs by
162
+ default, but you can also pass an argument to `check` indicating how many
163
+ examples `p` should be tested against.
164
+
165
+ ## Using Propr + Test Frameworks
166
+
167
+ Mixing in a module magically defines the `property` singleton method, so
168
+ you can use it to generate test cases.
169
+
170
+ ```ruby
171
+ describe "foo" do
172
+ include Propr::RSpec
173
+
174
+ # This defines three test cases, one per each `check`
175
+ property("length"){|a| a.length >= 0 }
176
+ check("abc").
177
+ check("xyz").
178
+ check{ String.random }
179
+ end
180
+ ```
181
+
182
+ Note your property should still return `true` or `false`. You should *not* use
183
+ `#should` or `#assert`, because the test generator will generate the assertion
184
+ for you. This also reduces visual clutter.
185
+
186
+ Alternatively, to use Propr with all specification, you can add this to your
187
+ `spec_helper.rb`
188
+
189
+ ```ruby
190
+ RSpec.configure do |config|
191
+ include Propr::RSpec
192
+ end
193
+ ```
194
+
195
+ ### Property DSL
196
+
197
+ The code block inside `property { ... }` has an extended scope that defines
198
+ a few helpful methods:
199
+
200
+ * __guard__: Skip this iteration unless all the given conditions are met. This
201
+ can be used, for instance, to define a property only on even integers.
202
+ `property{|x| guard(x.even?); x & 1 == 0 }`
203
+
204
+ * __error?__: True if the code block throws an exception of the given type.
205
+ `property{|x| error? { x / 0 }}`
206
+
207
+ * __m__: Short alias for `Propr::Random`, used to generate random data as described
208
+ below.
209
+ `property{|x| m.eval(m.sequence([m.unit 0] * x)).length == x }`
210
+
211
+ ### Check DSL
212
+
213
+ The code block inside `check { ... }` should return a generator value. The code
214
+ block's scope is extended with a few combinators to compose generators.
215
+
216
+ * __unit__: Create a generator that returns the given value. For instance, to yield
217
+ `3` as an argument to the property,
218
+ `check { unit(3) }`
219
+
220
+ * __bind__: Chain the value yielded by one generator into another. For instance, to
221
+ yield two integers as arguments to a property,
222
+ `check { bind(Integer.random){|a| bind(Integer.random){|b| unit([a,b]) }}}`
223
+
224
+ * __guard__: Short-circuit the chain if the given condition is false. The entire chain
225
+ will be re-run until the guard passes. For instance, to generate two distinct numbers,
226
+ `check { bind(Integer.random){|a| bind(Integer.random){|b| guard(a != b){ unit([a,b]) }}}}`
227
+
228
+ * __join__: Remove one level of generator nesting. If you have a generator `x` that
229
+ *yields* a number generator, then `join x` is a number generator. For instance, to yield
230
+ either a number or a string,
231
+ `check { join([Integer.random, String.random].random) }`
232
+
233
+ * __sequence__: Convert a list of generator values to a list generator. For instance, to
234
+ yield three integers to a property,
235
+ `check { sequence [Integer.random]*3 }`
236
+
237
+ ## Generating Random Values
238
+
239
+ Propr defines a `random` method that returns a generator for most standard
240
+ Ruby types. You can run the generator using the `Propr::Random.eval` method.
241
+
242
+ >> m = Propr::Random
243
+ => ...
244
+
245
+ >> m.eval(Boolean.random)
246
+ => false
247
+
248
+ ### Boolean
249
+
250
+ >> m.eval Boolean.random
251
+ => true
252
+
253
+ ### Date
254
+
255
+ >> m.eval(Date.random(min: Date.today - 10, max: Date.today + 10)).to_s
256
+ => "2012-03-01"
257
+
258
+ Options
259
+
260
+ * `min:` minimum value, defaults to 0001-01-01
261
+ * `max:` maximum value, defaults to 9999-12-31
262
+ * `center:` defaults to the midpoint between min and max
263
+
264
+ ### Time
265
+
266
+ >> m.eval Time.random(min: Time.now, max: Time.now + 3600)
267
+ => 2012-02-20 13:47:57 -0600
268
+
269
+ Options
270
+
271
+ * `min:` minimum value, defaults to 1000-01-01 00:00:00 UTC
272
+ * `max:` maximum value, defaults to 9999-12-31 12:59:59 UTC
273
+ * `center:` defaults to the midpoint between min and max
274
+
275
+ ### String
276
+
277
+ >> m.eval String.random(min: 5, max: 10, charset: :lower)
278
+ => "rqyhw"
279
+
280
+ Options
281
+
282
+ * `min:` minimum size, defaults to 0
283
+ * `max:` maximum size, defaults to 10
284
+ * `center:` defaults to the midpoint between min and max
285
+ * `charset:` regular expression character class, defaults to `/[[:print]]/`
286
+
287
+ ### Numbers
288
+
289
+ #### Integer.random
290
+
291
+ >> m.eval Integer.random(min: -500, max: 500)
292
+ => -382
293
+
294
+ Options
295
+
296
+ * `min:` minimum value, defaults to Integer::MIN
297
+ * `max:` maximum value, defaults to Integer::MAX
298
+ * `center:` defaults to the midpoint between min and max.
299
+
300
+ #### Float.random
301
+
302
+ >> m.eval Float.random(min: -500, max: 500)
303
+ => 48.252030464134364
304
+
305
+ Options
306
+
307
+ * `min:` minimum value, defaults to -Float::MAX
308
+ * `max:` maximum value, defaults to Float::MAX
309
+ * `center:` defaults to the midpoint between min and max.
310
+
311
+ #### Rational.random
312
+
313
+ >> m.eval m.bind(m.sequence [Integer.random]*2){|a,b| unit Rational(a,b) }
314
+ => (300421843/443649464)
315
+
316
+ Not implemented, as there isn't a nice way to ensure a `min` works. Instead,
317
+ generate two numeric values and combine them:
318
+
319
+ #### BigDecimal.random
320
+
321
+ >> m.eval(BigDecimal.random(min: 10, max: 20)).to_s("F")
322
+ => "14.934854011762374703280016489856414847259220844969789892"
323
+
324
+ Options
325
+
326
+ * `min:` minimum value, defaults to -Float::MAX
327
+ * `max:` maximum value, defaults to Float::MAX
328
+ * `center:` defaults to the midpoint between min and max
329
+
330
+ #### Bignum.random
331
+
332
+ >> m.eval Integer.random(min: Integer::MAX, max: Integer::MAX * 2)
333
+ => 2015151263
334
+
335
+ There's no constructor specifically for Bignum. You can use `Integer.random`
336
+ and specify `min: Integer::MAX + 1` and some even larger `max` value. Ruby
337
+ will automatically handle Integer overflow by coercing to Bignum.
338
+
339
+ #### Complex.random
340
+
341
+ >> m.eval(m.bind(m.sequence [Float.random(min:-10, max:10)]*2){|a,b| m.unit Complex(a,b) })
342
+ => (9.806161068637833+7.523520738439842i)
343
+
344
+ Not implemented, as there's no simple way to implement min and max, nor the types
345
+ of the components. Instead, generate two numeric values and combine them:
346
+
347
+ ### Collections
348
+
349
+ The class method `random` returns a generator to construct a collection of
350
+ elements, while the `#random` instance method returns a generator which returns
351
+ an element from the collection.
352
+
353
+ #### Array.random
354
+
355
+ Expects a block parameter that yields a generator for elements.
356
+
357
+ >> m.eval Array.random(min:4, max:4) { String.random(min:4, max:4) }
358
+ => ["2n #", "UZ1d", "0vF,", "cV_{"]
359
+
360
+ Options
361
+
362
+ * `min:` minimum size, defaults to 0
363
+ * `max:` maximum size, defaults to 10
364
+ * `center:` defaults to the midpoint between min and max
365
+
366
+ #### Hash.random
367
+
368
+ Expects a block parameter that yields generator of [key, value] pairs.
369
+
370
+ >> m.eval Hash.random(min:2, max:4) { m.sequence [Integer.random, m.unit(nil)] }
371
+ => {564854752=>nil, -1065292239=>nil, 830081146=>nil}
372
+
373
+ Options
374
+
375
+ * `min:` minimum size, defaults to 0
376
+ * `max:` maximum size, defaults to 10
377
+ * `center:` defaults to the midpoint between min and max
378
+
379
+ #### Hash.random_vals
380
+
381
+ Expects a hash whose keys are ordinary values, and whose values are
382
+ generators.
383
+
384
+ >> m.eval Hash.random_vals(a: String.random, b: Integer.random)
385
+ => {:a=>"Fi?p`g", :b=>4551738453396095365}
386
+
387
+ Doesn't accept any options.
388
+
389
+ #### Set.random
390
+
391
+ Expects a block parameter that yields a generator for elements.
392
+
393
+ >> m.eval Set.random(min:4, max:4) { String.random(min:4, max:4) }
394
+ => #<Set: {"2n #", "UZ1d", "0vF,", "cV_{"}>
395
+
396
+ Options
397
+
398
+ * `min:` minimum size, defaults to 0
399
+ * `max:` maximum size, defaults to 10
400
+ * `center:` defaults to the midpoint between min and max
401
+
402
+ #### Range.random
403
+
404
+ Expects __either__ a block parameter __or__ one or both of min and max.
405
+
406
+ >> m.eval Range.random(min: 0, max: 100)
407
+ => 81..58
408
+
409
+ >> m.eval Range.random { Integer.random(min: 0, max: 100) }
410
+ => 9..80
411
+
412
+ Options
413
+
414
+ * `min:` minimum element
415
+ * `max:` maximum element
416
+ * `inclusive?:` defaults to true, meaning Range includes max element
417
+
418
+ #### Elements from a collection
419
+
420
+ The `#random` instance method is defined on the above types. It takes no parameters.
421
+
422
+ >> m.eval([1,2,3,4,5].random)
423
+ => 4
424
+
425
+ >> m.eval({a: 1, b: 2, c: 3, d: 4}.random)
426
+ => [:b, 2]
427
+
428
+ >> m.eval((0..100).random)
429
+ => 12
430
+
431
+ >> m.eval(Set.new([1,2,3,4]).random)
432
+ => 4
433
+
434
+ ## Attenuation (limiting the search space for counter examples)
435
+
436
+ The `m.eval` method has a second parameter that serves to exponentially reduce
437
+ the domain for generators, specified with `min:` and `max:` parameters. The scale
438
+ value may range from `0` to `1`, where `1` causes no change.
439
+
440
+ When scale is `0`, the domain is reduced to a single value, which is specified by
441
+ the `center:` parameter. Usually this defaults to the midpoint between `min:` and
442
+ `max:`. Any value between `min:` and `max:` can be given for `center:`, in addition
443
+ to the three symbolic values, `:min`, `:mid`, and `:max`.
444
+
445
+ Scale values beteween `0` and `1` adjust the domain exponentially, so a domain with
446
+ 10,000 elements when `scale = 1` will have 1,000 elements when `scale = 0.5` and
447
+ only 100 when `scale = 0.25`.
448
+
449
+ With `scale = 0`, the domain contains at most `10000^0 = 1` elements:
450
+
451
+ >> m.eval Integer.random(min: 0, max: 10000, center: :min), 0
452
+ == m.eval Integer.random(min: 0, max: 0)
453
+
454
+ >> m.eval Integer.random(min: 0, max: 10000, center: :mid), 0
455
+ == m.eval Integer.random(min: 5000, max: 5000)
456
+
457
+ >> m.eval Integer.random(min: 0, max: 10000, center: :max), 0
458
+ == m.eval Integer.random(min: 10000, max: 10000)
459
+
460
+ With `scale = 0.25`, the domain contains at most `10000^0.25 = 10` elements:
461
+
462
+ >> m.eval Integer.random(min: 0, max: 10000, center: :min), 0.25
463
+ == m.eval Integer.random(min: 0, max: 9)
464
+
465
+ >> m.eval Integer.random(min: 0, max: 10000, center: :mid), 0.25
466
+ == m.eval Integer.random(min: 4996, max: 5004)
467
+
468
+ >> m.eval Integer.random(min: 0, max: 10000, center: :max), 0.25
469
+ == m.eval Integer.random(min: 9991, max: 10000)
470
+
471
+ With `scale = 0.50`, the domain contains at most `10000^0.5 = 100` elements:
472
+
473
+ >> m.eval Integer.random(min: 0, max: 10000, center: :min), 0.5
474
+ == m.eval Integer.random(min: 0, max: 99)
475
+
476
+ >> m.eval Integer.random(min: 0, max: 10000, center: :mid), 0.5
477
+ == m.eval Integer.random(min: 4951, max: 5048)
478
+
479
+ >> m.eval Integer.random(min: 0, max: 10000, center: :max), 0.5
480
+ == m.eval Integer.random(min: 9901, max: 10000)
481
+
482
+ With `scale = 0.75`, the domain contains at most `10000^0.75 = 1000` elements:
483
+
484
+ >> m.eval Integer.random(min: 0, max: 10000, center: :min), 0.75
485
+ == m.eval Integer.random(min: 0, max: 998)
486
+
487
+ >> m.eval Integer.random(min: 0, max: 10000, center: :mid), 0.75
488
+ == m.eval Integer.random(min: 4507, max: 5499)
489
+
490
+ >> m.eval Integer.random(min: 0, max: 10000, center: :max), 0.75
491
+ == m.eval Integer.random(min: 9002, max: 10000)
492
+
493
+ ### Deepening of the Search Space
494
+
495
+ By default, the test framework adapters increase the scale linearly (causing
496
+ an exponential increase of the domain size) each time the property is tested.
497
+
498
+ That is, when running 100 iterations, scale values will be 0.00, 0.01, 0.02,
499
+ 0.03, 0.04, etc. This is intended to test the simplest counterexamples first,
500
+ and increase the complexity of generated inputs exponentially.
501
+
502
+ ### Simplification of Counterexamples
503
+
504
+ Once a random input has been classified as a counterexample, Propr will
505
+ search for a simpler counterexample. This is done by iteratively calling
506
+ `#shrink` on each successively smaller counterexample.
507
+
508
+ $ cat shrink.example
509
+ require "rspec"
510
+ require "propr"
511
+
512
+ RSpec.configure do |config|
513
+ include Propr::RSpec
514
+
515
+ srand 146211424375622429406889408197139382303
516
+ srand.tap{|seed| puts "Random seed is #{seed}"; srand seed }
517
+ end
518
+
519
+ describe Float do
520
+ property("assoc"){|x,y,z| (x + y) + z == x + (y + z) }
521
+ .check(-382863.98514407175, 224121.21177705095, 276118.77134001954)
522
+ end
523
+
524
+ $ rspec shrink.example
525
+ Random seed is 146211424375622429406889408197139382303
526
+ F
527
+
528
+ Failures:
529
+
530
+ 1) Float assoc
531
+ Propr::Falsifiable:
532
+ input: -382863.98514407175, 224121.21177705095, 276118.77134001954
533
+ shrunken: -0.007740960460133677, 0.011895728563551701, 3.9765678826328424e-05
534
+ after: 0 passed, 0 skipped
535
+ # ./lib/propr/rspec.rb:36:in `block in check'
536
+
537
+ Finished in 10.52 seconds
538
+ 1 example, 1 failure
539
+
540
+ Notice the output shows a "simpler" counterexample than the inputs we explicitly
541
+ tested. This becomes useful when testing with more complex data like trees, where
542
+ it can be difficult to understand which aspect of the counterexample is relevant.
543
+
544
+ ## More Reading
545
+
546
+ * [Presentation at KC Ruby Meetup Group](https://github.com/kputnam/presentations/raw/master/Property-Based-Testing.pdf)
547
+
548
+ ## Related Projects
549
+
550
+ * [Rantly](https://github.com/hayeah/rantly)
551
+ * [PropER](https://github.com/manopapad/proper)
552
+ * [QuviQ](http://www.quviq.com/documents/QuviqFlyer.pdf)
553
+ * [QuickCheck](http://www.haskell.org/haskellwiki/Introduction_to_QuickCheck)