mrproper 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|