prop_check 0.17.0 → 0.18.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.
- checksums.yaml +4 -4
- data/.github/workflows/run_tests.yaml +2 -2
- data/CHANGELOG.md +3 -0
- data/README.md +61 -20
- data/lib/prop_check/generator.rb +1 -1
- data/lib/prop_check/generators.rb +4 -4
- data/lib/prop_check/property.rb +5 -8
- data/lib/prop_check/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba078df3937d6f069a4ed24e9aaed9617dc1eb03139be0189e51b060b11a75ba
|
4
|
+
data.tar.gz: 0fe77f31d282754df6712aafae7995e82e99d9ee86f8ad12bd060596147dafe1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e49791f3e5a380e822f4d86f39444a09e26828c4b1ea7c90d298c9eedcc14d8c6fdc05864439e1984b0cead4183beb1ae108bee82c7ce6dccbe11aae5efbd248
|
7
|
+
data.tar.gz: cddd15cf550e166ec93d15fa2ffdf07a68646aa6750d544b6a5cf64c9bfb44177b638e6ff1df2ce25e28105df05aa838534e6eaafadefb7688aed32dae9d3bbc
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
- 0.18.0
|
2
|
+
- Features:
|
3
|
+
- Allows calling `PropCheck::Property#check` without a block, which will just return `self`. This is useful for writing wrapper functions that use `before/after/around/with_config` etc hooks which might themselves optionally want a block so they can be chained. (See the `forall_with_db` snippet in the README for an example)
|
1
4
|
- 0.17.0
|
2
5
|
- Features:
|
3
6
|
- Recursive generation using `PropCheck::Generators.tree`.
|
data/README.md
CHANGED
@@ -40,9 +40,9 @@ It works by generating arbitrary data matching your specification and checking t
|
|
40
40
|
|
41
41
|
Writing these kinds of tests usually consists of deciding on guarantees that your code should have -- properties that should always hold true, regardless of wat the world throws at you. Some examples are:
|
42
42
|
|
43
|
-
- Your code should
|
43
|
+
- Your code should never crash.
|
44
44
|
- If you remove an object, you can no longer see it
|
45
|
-
- If you serialize and then
|
45
|
+
- If you serialize and then deserialize a value, you get the same value back.
|
46
46
|
|
47
47
|
|
48
48
|
## Implemented and still missing features
|
@@ -107,7 +107,7 @@ PropCheck.forall(G.array(G.integer)) do |numbers|
|
|
107
107
|
|
108
108
|
# Check that no number is smaller than the previous number
|
109
109
|
sorted_numbers.each_cons(2) do |former, latter|
|
110
|
-
raise "Elements are not sorted! #{latter} is < #{former}" if latter
|
110
|
+
raise "Elements are not sorted! #{latter} is < #{former}" if latter > former
|
111
111
|
end
|
112
112
|
end
|
113
113
|
```
|
@@ -122,32 +122,53 @@ def naive_average(array)
|
|
122
122
|
array.sum / array.length
|
123
123
|
end
|
124
124
|
```
|
125
|
-
```ruby
|
126
|
-
# And then in a test case:
|
127
|
-
G = PropCheck::Generators
|
128
|
-
PropCheck.forall(numbers: G.array(G.integer)) do |numbers:|
|
129
|
-
result = naive_average(numbers)
|
130
|
-
unless result.is_a?(Integer) do
|
131
|
-
raise "Expected the average to be an integer!"
|
132
|
-
end
|
133
|
-
end
|
134
125
|
|
135
|
-
|
136
|
-
|
137
|
-
|
126
|
+
The test case, using RSpec:
|
127
|
+
``` ruby
|
128
|
+
require 'rspec'
|
129
|
+
|
130
|
+
RSpec.describe "#naive_average" do
|
138
131
|
G = PropCheck::Generators
|
139
132
|
|
140
133
|
it "returns an integer for any input" do
|
141
|
-
forall(
|
142
|
-
result = naive_average(numbers)
|
134
|
+
PropCheck.forall(G.array(G.integer)) do |numbers|
|
135
|
+
result = naive_average(numbers)
|
136
|
+
|
143
137
|
expect(result).to be_a(Integer)
|
144
138
|
end
|
145
139
|
end
|
146
140
|
end
|
147
141
|
```
|
148
142
|
|
149
|
-
|
143
|
+
The test case, using MiniTest:
|
144
|
+
``` ruby
|
145
|
+
require 'minitest/autorun'
|
146
|
+
class NaiveAverageTest < MiniTest::Unit::TestCase
|
147
|
+
G = PropCheck::Generators
|
148
|
+
|
149
|
+
def test_that_it_returns_an_integer_for_any_input()
|
150
|
+
PropCheck.forall(G.array(G.integer)) do |numbers|
|
151
|
+
result = naive_average(numbers)
|
152
|
+
|
153
|
+
assert_instance_of(Integer, result)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
```
|
150
158
|
|
159
|
+
The test case, using only vanilla Ruby:
|
160
|
+
```ruby
|
161
|
+
# And then in a test case:
|
162
|
+
G = PropCheck::Generators
|
163
|
+
|
164
|
+
PropCheck.forall(G.array(G.integer)) do |numbers|
|
165
|
+
result = naive_average(numbers)
|
166
|
+
|
167
|
+
raise "Expected the average to be an integer!" unless result.is_a?(Integer)
|
168
|
+
end
|
169
|
+
```
|
170
|
+
|
171
|
+
When running this particular example PropCheck very quickly finds out that we have made a programming mistake:
|
151
172
|
```ruby
|
152
173
|
ZeroDivisionError:
|
153
174
|
(after 6 successful property test runs)
|
@@ -270,6 +291,26 @@ although above are the most generally useful ones.
|
|
270
291
|
[PropCheck::Generator documentation](https://www.rubydoc.info/github/Qqwy/ruby-prop_check/master/PropCheck/Generator)
|
271
292
|
[PropCheck::Generators documentation](https://www.rubydoc.info/github/Qqwy/ruby-prop_check/master/PropCheck/Generators)
|
272
293
|
|
294
|
+
|
295
|
+
## Usage within Rails / with a database
|
296
|
+
|
297
|
+
Using PropCheck for unit tests in a Rails, Sinatra, Hanami, etc. project is very easy.
|
298
|
+
Here are some simple recommendations for the best results:
|
299
|
+
- Tests that do not need to use the DB at all are usually 10x-100x faster. Faster tests means that you can configure PropCheck to do more test runs.
|
300
|
+
- If you do need to use the database, use the [database_cleaner](https://github.com/DatabaseCleaner/database_cleaner) gem, preferibly with the fast `:transaction` strategy if your RDBMS supports it. To make sure the DB is cleaned around each generated example, you can write the following helper:
|
301
|
+
``` ruby
|
302
|
+
# Version of PropCheck.forall
|
303
|
+
# which ensures records persisted to the DB in one generated example
|
304
|
+
# do not affect any other
|
305
|
+
def forall_with_db(*args, **kwargs, &block)
|
306
|
+
PropCheck.forall(*args, **kwargs)
|
307
|
+
.before { DatabaseCleaner.start }
|
308
|
+
.after { DatabaseCleaner.clean }
|
309
|
+
.check(&block)
|
310
|
+
end
|
311
|
+
```
|
312
|
+
- Other setup/cleanup should also usually happen around each generated example rather than around the whole test: Instead of using the hooks exposed by RSpec/MiniTest/etc., use the before/after/around hooks exposed by PropCheck.
|
313
|
+
|
273
314
|
## Development
|
274
315
|
|
275
316
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -286,7 +327,7 @@ The gem is available as open source under the terms of the [MIT License](https:/
|
|
286
327
|
|
287
328
|
## Code of Conduct
|
288
329
|
|
289
|
-
Everyone interacting in the PropCheck project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/
|
330
|
+
Everyone interacting in the PropCheck project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/Qqwy/ruby-prop_check/blob/master/CODE_OF_CONDUCT.md).
|
290
331
|
|
291
332
|
## Attribution and Thanks
|
292
333
|
|
@@ -294,7 +335,7 @@ I want to thank the original creators of QuickCheck (Koen Claessen, John Hughes)
|
|
294
335
|
I also want to greatly thank Thomasz Kowal who made me excited about property based testing [with his great talk about stateful property testing](https://www.youtube.com/watch?v=q0wZzFUYCuM),
|
295
336
|
as well as Fred Herbert for his great book [Property-Based Testing with PropEr, Erlang and Elixir](https://propertesting.com/) which is really worth the read (regardless of what language you are using).
|
296
337
|
|
297
|
-
The implementation and API of PropCheck takes a lot of inspiration from the following
|
338
|
+
The implementation and API of PropCheck takes a lot of inspiration from the following projects:
|
298
339
|
|
299
340
|
- Haskell's [QuickCheck](https://hackage.haskell.org/package/QuickCheck) and [Hedgehog](https://hackage.haskell.org/package/hedgehog);
|
300
341
|
- Erlang's [PropEr](https://hex.pm/packages/proper);
|
data/lib/prop_check/generator.rb
CHANGED
@@ -116,7 +116,7 @@ module PropCheck
|
|
116
116
|
# and act on it.
|
117
117
|
#
|
118
118
|
# >> Generators.choose(0..100).with_config.map { |int, conf| Date.jd(conf[:default_epoch].jd + int) }.call(size: 10, rng: Random.new(42), config: PropCheck::Property::Configuration.new)
|
119
|
-
# => Date.new(2023, 01,
|
119
|
+
# => Date.new(2023, 01, 12)
|
120
120
|
def with_config
|
121
121
|
Generator.new do |**kwargs|
|
122
122
|
result = generate(**kwargs)
|
@@ -769,7 +769,7 @@ module PropCheck
|
|
769
769
|
# DateTimes start around the given `epoch:` and deviate more when `size` increases.
|
770
770
|
# when no epoch is set, `PropCheck::Property::Configuration.default_epoch` is used, which defaults to `DateTime.now`.
|
771
771
|
#
|
772
|
-
# >> PropCheck::Generators.datetime.sample(2, rng: Random.new(42), config: PropCheck::Property::Configuration.new)
|
772
|
+
# >> PropCheck::Generators.datetime(epoch: Date.new(2022, 11, 20)).sample(2, rng: Random.new(42), config: PropCheck::Property::Configuration.new)
|
773
773
|
# => [DateTime.parse("2022-11-17 07:11:59.999983907 +0000"), DateTime.parse("2022-11-19 05:27:16.363618076 +0000")]
|
774
774
|
def datetime(epoch: nil)
|
775
775
|
datetime_from_offset(real_float, epoch: epoch)
|
@@ -785,7 +785,7 @@ module PropCheck
|
|
785
785
|
##
|
786
786
|
# Variant of `#datetime` that only generates datetimes in the future (relative to `:epoch`).
|
787
787
|
#
|
788
|
-
# >> PropCheck::Generators.future_datetime.sample(2, rng: Random.new(42), config: PropCheck::Property::Configuration.new).map(&:inspect)
|
788
|
+
# >> PropCheck::Generators.future_datetime(epoch: Date.new(2022, 11, 20)).sample(2, rng: Random.new(42), config: PropCheck::Property::Configuration.new).map(&:inspect)
|
789
789
|
# => ["#<DateTime: 2022-11-21T16:48:00+00:00 ((2459905j,60480s,16093n),+0s,2299161j)>", "#<DateTime: 2022-11-19T18:32:43+00:00 ((2459903j,66763s,636381924n),+0s,2299161j)>"]
|
790
790
|
def future_datetime(epoch: nil)
|
791
791
|
datetime_from_offset(real_positive_float, epoch: epoch)
|
@@ -794,7 +794,7 @@ module PropCheck
|
|
794
794
|
##
|
795
795
|
# Variant of `#datetime` that only generates datetimes in the past (relative to `:epoch`).
|
796
796
|
#
|
797
|
-
# >> PropCheck::Generators.past_datetime.sample(2, rng: Random.new(42), config: PropCheck::Property::Configuration.new)
|
797
|
+
# >> PropCheck::Generators.past_datetime(epoch: Date.new(2022, 11, 20)).sample(2, rng: Random.new(42), config: PropCheck::Property::Configuration.new)
|
798
798
|
# => [DateTime.parse("2022-11-17 07:11:59.999983907 +0000"), DateTime.parse("2022-11-19 05:27:16.363618076 +0000")]
|
799
799
|
def past_datetime(epoch: nil)
|
800
800
|
datetime_from_offset(real_negative_float, epoch: epoch)
|
@@ -805,7 +805,7 @@ module PropCheck
|
|
805
805
|
# Times start around the given `epoch:` and deviate more when `size` increases.
|
806
806
|
# when no epoch is set, `PropCheck::Property::Configuration.default_epoch` is used, which defaults to `DateTime.now`.
|
807
807
|
#
|
808
|
-
# >> PropCheck::Generators.time.sample(2, rng: Random.new(42), config: PropCheck::Property::Configuration.new)
|
808
|
+
# >> PropCheck::Generators.time(epoch: Date.new(2022, 11, 20)).sample(2, rng: Random.new(42), config: PropCheck::Property::Configuration.new)
|
809
809
|
# => [DateTime.parse("2022-11-17 07:11:59.999983907 +0000").to_time, DateTime.parse("2022-11-19 05:27:16.363618076 +0000").to_time]
|
810
810
|
def time(epoch: nil)
|
811
811
|
datetime(epoch: epoch).map(&:to_time)
|
data/lib/prop_check/property.rb
CHANGED
@@ -42,11 +42,8 @@ module PropCheck
|
|
42
42
|
# of this class on before finally passing a block to it using `#check`.
|
43
43
|
# (so `forall(Generators.integer) do |val| ... end` and forall(Generators.integer).check do |val| ... end` are the same)
|
44
44
|
def self.forall(*bindings, **kwbindings, &block)
|
45
|
-
|
46
|
-
|
47
|
-
return property.check(&block) if block_given?
|
48
|
-
|
49
|
-
property
|
45
|
+
new(*bindings, **kwbindings)
|
46
|
+
.check(&block)
|
50
47
|
end
|
51
48
|
|
52
49
|
##
|
@@ -106,9 +103,7 @@ module PropCheck
|
|
106
103
|
duplicate.instance_variable_set(:@config, @config.merge(config))
|
107
104
|
duplicate.freeze
|
108
105
|
|
109
|
-
|
110
|
-
|
111
|
-
duplicate
|
106
|
+
duplicate.check(&block)
|
112
107
|
end
|
113
108
|
|
114
109
|
##
|
@@ -248,6 +243,8 @@ module PropCheck
|
|
248
243
|
##
|
249
244
|
# Checks the property (after settings have been altered using the other instance methods in this class.)
|
250
245
|
def check(&block)
|
246
|
+
return self unless block_given?
|
247
|
+
|
251
248
|
n_runs = 0
|
252
249
|
n_successful = 0
|
253
250
|
|
data/lib/prop_check/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: prop_check
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.18.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Qqwy/Wiebe-Marten Wijnja
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-11-
|
11
|
+
date: 2022-11-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: amazing_print
|