mrproper 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+
@@ -0,0 +1,6 @@
1
+ require 'minitest/autorun'
2
+ require 'mrproper/base'
3
+ require 'mrproper/dsl'
4
+ require 'mrproper/core_extensions/string_extensions'
5
+
6
+ include MrProper
@@ -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
@@ -0,0 +1,7 @@
1
+ class String
2
+ @@chars ||= ('a'..'z').to_a + ('A'..'Z').to_a
3
+
4
+ def self.random(size = 8)
5
+ (0..size).map{ @@chars[rand(@@chars.length)] }.join
6
+ end
7
+ end
@@ -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: []