NOTE: this is abandonware. It was a nice project to learn about the topic and fun code to write, especially the data generation DSL. But it hasn't been updated in a long while, it's not very feature rich and doesn't integrate very nicely with other test libraries. The best option nowadays to do property based testing in Ruby is clearly rantly. I encourage you to take a look!
And now, for archeology purposes, here is the original README and code:
MrProper is a Test::Unit-based library to do Property Based Testing a la Haskell's QuickCheck.
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:
- Define the kind of data your function/method is supposed to accept
- Define predicates (properties) your function/method is supposed to comply with
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.
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:
require 'mrproper'
properties 'double' do
data Integer
data Float
property 'is the same as adding twice' do |data|
assert_equal data + data, double(data)
end
end
Running the properties
After implementing double
(we'll leave that as an exercise ;)
), we run the properties as a regular test file:
$ testrb double_properties.rb
Run options:
# Running tests:
.
Finished tests in 0.001625s, 615.3846 tests/s, 123076.9231 assertions/s.
1 tests, 200 assertions, 0 failures, 0 errors, 0 skips
Only one test runs, but notice the insane number of assertions: a lot of random Integer
and Float
values generated for you.
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:
def double(i)
return -666 if i > 20
i * 2
end
And run the properties again:
$ testrb double_properties.rb
Run options:
# Running tests:
F
Finished tests in 0.030735s, 32.5362 tests/s, 97.6086 assertions/s.
1) Failure:
test_property: "is the same as adding twice"
Property "is the same as adding twice" is falsable for data 39
Expected: 78
Actual: -666
1 tests, 3 assertions, 1 failures, 0 errors, 0 skips
Data generation DSL
In addition to plain class names, we can feed data
with more or less complex expressions to define data structures:
data [String] # generates arrays of strings such as
# ["QC", "QyNYF", "ZRHehcux", "HPos"]
# ["arnyaZWp", "U"] (or [])
data [Integer, String] # generates arrays of one integer and
# one string such as [-89, "cDoZZRzGco"]
# or [-44, "eB"]
data [[Float]] # generates arrays of arrays of floats
# such as [[0.12, 3.41], [-2.31]]
data (1..10) # generates integers between 1 and 10
data (0.0..10.0) # generates floats between 0 and 10
data({Symbol => String}) # generates hashes whose keys are symbols
# and whose values are strings such as
# {:tR=>"m", :aSKnsndwWK=>"QUrGwAAh"}
# (in the hashes example, we need to use
# parenthesis because otherwise the Ruby
# parser thinks we're defining a block `;)`)
data({String => [Integer]}) # generates hashes whose keys are strings
# and whose values are arrays of integers
data({:first => String,
:second => Integer}) # generates a hash with two keys, :first and
# :second, each one with an String or Integer
# value (this is specially useful for testing
# methods with more than one parameter)
You can combine this cases ad infinitum, but in case this is not enough, you can just use a block and do whatever you want to generate the data:
data do
rand > 0.5 ? Wadus.new(rand(9)) : FooBar.new(rand(9))
end
Further reading
- Slides of a quick and dirty pesentation about MrProper at railscamp-es '2011. Those slides eventually became this README's first version
- Chapter of the book “Real World Haskell” talking about QuickCheck
- RushCheck, an old (and apparently abandoned) implementation of this idea in Ruby
- rqc, a Ruby port of QC.js (a property-based testing framework in Javascript)
- ProTest, another implementation in Erlang
License
Released under the MIT license
Credits
Created during railscamp-es '2011 by Sergio Gil (@porras) and Mari Carmen Gutiérrez (@valakirka), with ideas from Luismi Cavallé (@cavalle) and feedback from many other atendees. Thank you everyone!