mrproper 0.0.1
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.
- data/README.md +106 -0
- data/lib/mrproper.rb +6 -0
- data/lib/mrproper/base.rb +25 -0
- data/lib/mrproper/core_extensions/string_extensions.rb +7 -0
- data/lib/mrproper/dsl.rb +61 -0
- metadata +53 -0
data/README.md
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# MrProper
|
2
|
+
|
3
|
+
MrProper is a [MiniTest](http://rubydoc.info/stdlib/minitest/1.9.2/frames)-based library to do Property Based Testing a la [Haskell](http://haskell.org/haskellwiki/Haskell)'s [QuickCheck](http://hackage.haskell.org/package/QuickCheck).
|
4
|
+
|
5
|
+
Property Based Testing is an alternative approach to unit testing for testing functional style functions/methods. Instead of using examples and testing the return value of your function for each example, you:
|
6
|
+
|
7
|
+
1. Define the kind of data your function/method is supposed to accept
|
8
|
+
2. Define predicates (properties) your function/method is supposed to comply with
|
9
|
+
|
10
|
+
Then MrProper uses that info to randomly check lots of test cases so that you can find extra edge cases you might have forgotten in your unit tests or implementation.
|
11
|
+
|
12
|
+
In order to do so, MrProper provides a very simple DSL, with just three methods, `properties`, `data` and `property`. For example, we could describe a `double` function in terms of properties:
|
13
|
+
|
14
|
+
require 'mrproper'
|
15
|
+
|
16
|
+
properties 'double' do
|
17
|
+
data Integer
|
18
|
+
data Float
|
19
|
+
|
20
|
+
property 'is the same as adding twice' do |data|
|
21
|
+
assert_equal data + data, double(data)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
## Running the properties
|
26
|
+
|
27
|
+
After implementing `double` (we'll leave that as an exercise `;)`), we run the properties as a regular MiniTest test file:
|
28
|
+
|
29
|
+
$ testrb double_properties.rb
|
30
|
+
Run options:
|
31
|
+
|
32
|
+
# Running tests:
|
33
|
+
|
34
|
+
.
|
35
|
+
|
36
|
+
Finished tests in 0.001625s, 615.3846 tests/s, 123076.9231 assertions/s.
|
37
|
+
|
38
|
+
1 tests, 200 assertions, 0 failures, 0 errors, 0 skips
|
39
|
+
|
40
|
+
Only one test runs, but notice the insane number of assertions: a lot of random `Integer` and `Float` values generated for you.
|
41
|
+
|
42
|
+
If we happen to have a buggy `double` implementation which fails for numbers greater than 20 (we're crappy developers, thats why we want tests!), MrProper will tell you the first case it finds that proves the property false:
|
43
|
+
|
44
|
+
def double(i)
|
45
|
+
return -666 if i > 20
|
46
|
+
i * 2
|
47
|
+
end
|
48
|
+
|
49
|
+
And run the properties again:
|
50
|
+
|
51
|
+
$ testrb double_properties.rb
|
52
|
+
Run options:
|
53
|
+
|
54
|
+
# Running tests:
|
55
|
+
|
56
|
+
F
|
57
|
+
|
58
|
+
Finished tests in 0.030735s, 32.5362 tests/s, 97.6086 assertions/s.
|
59
|
+
|
60
|
+
1) Failure:
|
61
|
+
test_property: "is the same as adding twice"
|
62
|
+
Property "is the same as adding twice" is falsable for data 39
|
63
|
+
Expected: 78
|
64
|
+
Actual: -666
|
65
|
+
|
66
|
+
1 tests, 3 assertions, 1 failures, 0 errors, 0 skips
|
67
|
+
|
68
|
+
## Data generation DSL
|
69
|
+
|
70
|
+
In addition to plain class names, we can feed `data` with more or less complex expressions to define data structures:
|
71
|
+
|
72
|
+
data [String] # generates arrays of strings such as
|
73
|
+
# ["QC", "QyNYF", "ZRHehcux", "HPos"]
|
74
|
+
# ["arnyaZWp", "U"] (or [])
|
75
|
+
data [Integer, String] # generates arrays of one integer and
|
76
|
+
# one string such as [-89, "cDoZZRzGco"]
|
77
|
+
# or [-44, "eB"]
|
78
|
+
data [[Float]] # generates arrays of arrays of floats
|
79
|
+
# such as [[0.12, 3.41], [-2.31]]
|
80
|
+
data({Symbol => String}) # generates hashes whose keys are symbols
|
81
|
+
# and whose values are strings such as
|
82
|
+
# {:tR=>"m", :aSKnsndwWK=>"QUrGwAAh"}
|
83
|
+
# (in the hashes example, we need to use
|
84
|
+
# parenthesis because otherwise the Ruby
|
85
|
+
# parser thinks we're defining a block `;)`)
|
86
|
+
data({String => [Integer]}) # generates hashes whose keys are strings
|
87
|
+
# and whose values are arrays of integers
|
88
|
+
|
89
|
+
In case this is not enough, you can just use a block and do whatever you want to generate the data:
|
90
|
+
|
91
|
+
data do
|
92
|
+
rand > 0.5 ? Wadus.new(rand(9)) : FooBar.new(rand(9))
|
93
|
+
end
|
94
|
+
|
95
|
+
## Todo
|
96
|
+
|
97
|
+
* Make it work in other rubies than 1.9.3
|
98
|
+
|
99
|
+
## License
|
100
|
+
|
101
|
+
Released under the [MIT license](http://github.com/porras/property/blob/master/LICENSE)
|
102
|
+
|
103
|
+
## Credits
|
104
|
+
|
105
|
+
Created during [railscamp-es](https://rails-camp-es.jottit.com/) '2011 by Sergio Gil ([@porras](http://github.com/porras)) and Mari Carmen Gutiérrez ([@valakirka](http://github.com/valakirka)), with ideas from Luismi Cavallé ([@cavalle](http://github.com/cavalle)) and feedback from many other atendees. Thank you everyone!
|
106
|
+
|
data/lib/mrproper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module MrProper
|
2
|
+
|
3
|
+
def properties(name, &block)
|
4
|
+
dsl = MrProper::DSL.new
|
5
|
+
dsl.instance_eval(&block)
|
6
|
+
Class.new(MiniTest::Unit::TestCase).class_eval do
|
7
|
+
include PropertiesHelper if const_defined?(:PropertiesHelper)
|
8
|
+
dsl.properties.each do |message, test_block|
|
9
|
+
define_method "test_property: #{message.inspect}" do
|
10
|
+
dsl.data_blocks.each do |data_block|
|
11
|
+
TESTS_PER_PROPERTY.times do
|
12
|
+
data = data_block.call
|
13
|
+
begin
|
14
|
+
instance_exec(data, &test_block)
|
15
|
+
rescue MiniTest::Assertion => e
|
16
|
+
raise MiniTest::Assertion.new("Property #{message.inspect} is falsable for data #{data.inspect}\n#{e.message}")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
data/lib/mrproper/dsl.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
module MrProper
|
2
|
+
|
3
|
+
TESTS_PER_PROPERTY = 100
|
4
|
+
|
5
|
+
class DSL
|
6
|
+
|
7
|
+
attr_reader :properties, :data_blocks
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@properties = []
|
11
|
+
@data_blocks = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def data(spec = nil, &block)
|
15
|
+
@data_blocks << data_block(spec, &block)
|
16
|
+
end
|
17
|
+
|
18
|
+
def property(message, &block)
|
19
|
+
@properties << [message, block]
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def data_block(spec = nil, &block)
|
25
|
+
return block if block_given?
|
26
|
+
|
27
|
+
case spec
|
28
|
+
when Array
|
29
|
+
if spec.size == 1
|
30
|
+
Proc.new { rand(20).times.map { data_block(spec.first).call } }
|
31
|
+
else
|
32
|
+
Proc.new { spec.map { |spec| data_block(spec).call }}
|
33
|
+
end
|
34
|
+
when Hash
|
35
|
+
Proc.new do
|
36
|
+
{}.tap do |h|
|
37
|
+
rand(20).times.each do
|
38
|
+
h[data_block(spec.keys.first).call] = data_block(spec.values.first).call
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
when Class
|
43
|
+
if spec == Integer
|
44
|
+
Proc.new { rand(1000) - 500 }
|
45
|
+
elsif spec == Float
|
46
|
+
Proc.new { rand * 10 - 10 }
|
47
|
+
elsif spec == String
|
48
|
+
Proc.new { String.random(rand(10)) }
|
49
|
+
elsif spec == Symbol
|
50
|
+
Proc.new { data_block(String).call.to_sym }
|
51
|
+
elsif spec == NilClass
|
52
|
+
Proc.new { nil }
|
53
|
+
end
|
54
|
+
else
|
55
|
+
Proc.new { spec }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
metadata
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mrproper
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Sergio Gil
|
9
|
+
- Mari Carmen Gutiérrez
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2011-11-06 00:00:00.000000000 Z
|
14
|
+
dependencies: []
|
15
|
+
description:
|
16
|
+
email: sgilperez@gmail.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files:
|
20
|
+
- README.md
|
21
|
+
files:
|
22
|
+
- README.md
|
23
|
+
- lib/mrproper/base.rb
|
24
|
+
- lib/mrproper/core_extensions/string_extensions.rb
|
25
|
+
- lib/mrproper/dsl.rb
|
26
|
+
- lib/mrproper.rb
|
27
|
+
homepage: http://github.com/porras/mrproper
|
28
|
+
licenses: []
|
29
|
+
post_install_message:
|
30
|
+
rdoc_options:
|
31
|
+
- --main
|
32
|
+
- README.md
|
33
|
+
require_paths:
|
34
|
+
- lib
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
36
|
+
none: false
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
requirements: []
|
48
|
+
rubyforge_project:
|
49
|
+
rubygems_version: 1.8.10
|
50
|
+
signing_key:
|
51
|
+
specification_version: 3
|
52
|
+
summary: Property Based Testing library
|
53
|
+
test_files: []
|