RickCheck -- automated testing of general assertions

Hej hej, fellow Rubyists!

First of all, I’d like to say that my experience with Ruby stretches
no longer than to about a week ago. I found Ruby very easy to learn,
and generally very fun to use! Thanks for contributing to great
things.

That said, I want to talk about testing general assertions
automatically. By ``general assertions’', I mean assertions that
assert some property of arbitrary objects. For example, say we wanted
to test this function:

def random_in_range(min, max)
entries = (min…max).entries
length = entries.length
entries[rand(length)]
end

We could define a Test::Unit test case:

class RandomFromTest < Test::Unit::TestCase
def test_random_from
random = random_in_range(5, 60)
assert(random >= 5, “#{random} >= 5”)
assert(random <= 60, “#{random} <= 60”)
end
end

We run it and see this:

Loaded suite RandomFromTest
Started
.
Finished in 0.001889 seconds.

1 tests, 2 assertions, 0 failures, 0 errors

And we could define a general assertion. I’ll show you how, first,
and then tell you what’s going on.

class RandomFromTest < QuickCase
def test_random_from
random = random_in_range(5, 60)
assert(random >= 5, “#{random} >= 5”)
assert(random <= 60, “#{random} <= 60”)
end

def prop_random_from(a, b)
yield [Integer, Integer]
random = random_in_range(a, b)
assert(random >= a, “#{random} >= #{a}”)
assert(random <= b, “#{random} <= #{b}”)
end
end

We run it, and get:

Loaded suite RandomFromTest
Started
.E
Finished in 0.003386 seconds.

  1. Error:
    prop_random_from(RandomFromTest):
    NoMethodError: undefined method >=' for nil:NilClass ./rcheck.rb:49:in call’ (given [233, 86])
    ./rcheck.rb:80:in test_arbitrary_cases' ./rcheck.rb:77:in times’
    ./rcheck.rb:77:in test_arbitrary_cases' ./rcheck.rb:90:in verify_method’
    ./rcheck.rb:156:in `run’

2 tests, 4 assertions, 0 failures, 1 errors

Whoops! Why’s that? Thinking through random_from tells us that it’s
because (223…66) makes a Range of no entries! We probably want
random_from to handle backwardsy stuff, too. So we hack away!

def random_in_range(a, b)
min, max = [a, b].sort
entries = (min…max).entries
length = entries.length
entries[rand(length)]
end

Running the test now produces

  1. Failure:
    prop_random_from(RandomFromTest)
    [./rcheck.rb:49:in call' (given [-26, -42]) ./rcheck.rb:80:in test_arbitrary_cases’
    ./rcheck.rb:77:in times' ./rcheck.rb:77:in test_arbitrary_cases’
    ./rcheck.rb:90:in verify_method' ./rcheck.rb:156:in run’]:
    -26 <= -42.
    is not true.

This reflects an unfortunate truth about tests: they can be buggy,
too! (I didn’t expect this unromantic twist, but hey, that’s honesty
for you.)

The problem is that the test needs to know about the ordering, too.
Else it can treat the smallest number as the maximum. Here’s a better
test:

def prop_random_from(a, b)
yield [Integer, Integer]
min, max = [a, b].sort
random = random_in_range(a, b)
assert(random >= min, “#{random} >= #{a}”)
assert(random <= max, “#{random} <= #{b}”)
end

This produces the pleasant:

Loaded suite RandomFromTest
Started

Finished in 0.663505 seconds.

2 tests, 1002 assertions, 0 failures, 0 errors

Yay! OK, I’ll explain how it works, now.

The whole point of this library is to automatically test some
assertions for arbitrary inputs. Hence, we need a way to get
arbitrary inputs. But we can’t really do that without knowing what
kinds of inputs the function expects. Therefore, the function needs
to tell the library what it wants. I chose to use yield for this.

To say what you want, you yield some `type specifiers’. This probably
brings images of static typing, complete with whips and straps. But
even the statically inclined have good ideas once in a while!

To be honest, I don’t have anything to do with this idea in
particular. I stole it all from QuickCheck[1]. But I digress.

A type specifier can be a class. This is a type specifier: Integer.
Or it can be a compound of stuff. This is also a type specifier:
[Integer, {:max => 40}].

After you’ve told the library what you want, it’ll make some of that,
and call you a bunch of times with it. A whole bunch of times – 500
times, by default! It doesn’t actually know how to generate data
itself. For that, it sends `arbitrary’ to the class you requested.

Here’s the definition of Integer#arbitrary:

class Integer
def Integer.arbitrary(opts = {})
min = opts[:min] || -100
max = opts[:max] || 500
if min == max then
min
else
rand(max - min) + min
end
end
end

So you see, the default minimums and maximums are -100 and 500. Good
to know.

Maybe you also see that it’s very easy to define your own arbitrary
methods. Which is a good thing, because the ones I defined kind of
stink. Here’s some typical output from String#arbitrary:

irb(main):001:0> String.arbitrary
=> “We have already seen that the theory of syntactic features
developed earlier does not readily tolerate the strong generative
capacity of the theory.”

irb(main):002:0> String.arbitrary =>
“Furthermore, an important property of these three types of EC is not
to be considered in determining the strong generative capacity of the
theory.”

irb(main):003:0> String.arbitrary =>
“For one thing, the fundamental error of regarding functional notions
as categorial is unspecified with respect to a stipulation to place
the constructions into these various categories.”

If you’ve read Chomsky, you might recognize the style. Anyways, I
plan to include more useful arbitraries in the future.

Enough jabbering. The code’s at
http://www.phubuh.org/rickcheck.tar.bz2, or if you wanna, you can grab
it from my Darcs[2] repository:

darcs get --verbose http://www.phubuh.org/repos/rickcheck

I’d love any feedback! Have fun!

Footnotes:
[1] Data- och informationsteknik
[2] İddaa Bahis Siteleri - Burada En İyi Siteler Var

···


brought to you by Mikael `phubuh’ Brockman!
DE! MORE CODE! MORE CODE! MORE CODE! MORE CODE! MORE CODE! MORE CO

phubuh@phubuh.org wrote:

Hej hej, fellow Rubyists!

Hej Hej Mikael,

That said, I want to talk about testing general assertions
automatically. By ``general assertions’', I mean assertions that
assert some property of arbitrary objects. For example, say we wanted
to test this function:

Nice! Glad to see some work in this area; I use random testing of
properties often and it’s a very powerful technique.

You might wanna check out the now a bit dated AutoTest stuff which seems
similar to your RickCheck but takes a somewhat different approach using
Generators that can generate different types of data. This seems a bit
more flexible than your approach since you can have multiple Generators
for objects of a certain type. OTOH, it’s more verbose; would be
interesting to merge our approaches and have it integrate nicely with
Test::Unit (which I think yours might already be doing?). You can check
out the annoucement in [ruby-talk:10855] and the code is here

Related to this is also paper 2 in my thesis (available at
Data- och informationsteknik) which evolves
tests by combining test cells (basically Generators above) into more
complex test behaviors. I am currently working on extending these ideas.

Regards and sorry for the plug!

/Robert Feldt

Robert Feldt feldt@ce.chalmers.se writes:

That said, I want to talk about testing general assertions
automatically. By ``general assertions’', I mean assertions that
assert some property of arbitrary objects. For example, say we wanted
to test this function:

Nice! Glad to see some work in this area; I use random testing of
properties often and it’s a very powerful technique.

Indeed, it is! I was kind of sceptical at the potential use of it at
first, but it turns out it’s pretty easy to come up with general
assertions for lots of problems. I think I’m going to be using it
lots.

You might wanna check out the now a bit dated AutoTest stuff which
seems similar to your RickCheck but takes a somewhat different
approach using Generators that can generate different types of
data. This seems a bit more flexible than your approach since you can
have multiple Generators for objects of a certain type.

Ooh, that’s interesting. I think it’s probably better to keep the
generation out of the classes themselves. Or maybe the default
generator could be tacked onto the class, to make the common case
easier.

OTOH, it’s more verbose; would be interesting to merge our
approaches

Definitely!

and have it integrate nicely with Test::Unit (which I think yours
might already be doing?).

Yup, it does. It defines a subclass of TestCase called QuickCase,
which adds automatical testing of prop_* methods. What I like best
about RickCheck is how easy it is to use for small stuff, and how it
blends nicely with Test::Unit:

class MyTest < QuickCase # most contrived example ever
def prop_foo(a)
yield [Integer]
assert_equal(a, (a + 1) - 1)
end

def test_bar
assert_equal(4, (4 * 2) / 2)
end
end

But the use of yield seems weird now, having seen AutoTest. In
retrospect, I can’t believe I didn’t think of just doing:

def test_foo
assert_property(Integer) do |a|
assert_equal(a, (a + 1) - 1)
end
end

I think that way is more natural, and it doesn’t need as many changes
to Test::Unit – test_foo is just a regular test. And it’s just one
line longer. :slight_smile:

You can check out the annoucement in [ruby-talk:10855] and the code
is here http://www.ce.chalmers.se/~feldt/ruby/extensions/autotest/

Very nice.

Related to this is also paper 2 in my thesis (available at
http://www.ce.chalmers.se/~feldt/thesis/08_paper2_wise.ps) which
evolves tests by combining test cells (basically Generators above)
into more complex test behaviors. I am currently working on extending
these ideas.

Wow! Most of that is way over my head, but it seems extraordinarily
cool. Using biological ideas in computing always struck me as a
beautiful idea. I’m glad we have guys like you on the Ruby boat. :slight_smile:

RickCheck used to do special treatment of `border cases’; for example,
Integer had this definition:

class Integer
def border_cases
[1, 0, -1]
end
end

But I removed it for some reason that I seem to have forgotten. I’d
like to add it back in, but I’m not sure what’s the best way to do it.
But it’s certainly a Good Thing to test the border cases as soon as
possible; they’re the most likely to screw up, after all.

Another thing I’d like to add is manual seeding of the random number
generator. Every test run could tell you what it was seeded with, so
that you can easily reproduce it later.

Well, I’ve babbled enough. Kudos!

···

phubuh@phubuh.org wrote:


/o\ /¯¯¯_
o = _/¯
\o/ ___/¯¯¯_/

Mikael Brockman wrote:

You might wanna check out the now a bit dated AutoTest stuff which
seems similar to your RickCheck but takes a somewhat different
approach using Generators that can generate different types of
data. This seems a bit more flexible than your approach since you can
have multiple Generators for objects of a certain type.

Ooh, that’s interesting. I think it’s probably better to keep the
generation out of the classes themselves. Or maybe the default
generator could be tacked onto the class, to make the common case
easier.

Yes, or we could even

class Object
def rand_instance # or call it arbitrary if you want…
__rand_generator.generate
end

def __rand_generator
@@__rand_generator ||= __get_rand_generator
end

def __get_rand_generator
gens =
ObjectSpace.each_object(Class) do |c|
if c.ancestors.include?(Generator) && c.generates?(self.class)
gens << c
end
end
OrGenerator.new(*gens)
end
end

so that it will use any loaded generators that can generate objects of
its own class.

OTOH, it’s more verbose; would be interesting to merge our
approaches

Definitely!

Ok, lets do it. May I propose a different name? QuickcheckR or
Test::Random? I think the latter is actually better…

and have it integrate nicely with Test::Unit (which I think yours
might already be doing?).

Yup, it does. It defines a subclass of TestCase called QuickCase,
which adds automatical testing of prop_* methods. What I like best
about RickCheck is how easy it is to use for small stuff, and how it
blends nicely with Test::Unit:

class MyTest < QuickCase # most contrived example ever
def prop_foo(a)
yield [Integer]
assert_equal(a, (a + 1) - 1)
end

def test_bar
assert_equal(4, (4 * 2) / 2)
end
end

But the use of yield seems weird now, having seen AutoTest. In
retrospect, I can’t believe I didn’t think of just doing:

def test_foo
assert_property(Integer) do |a|
assert_equal(a, (a + 1) - 1)
end
end

I think that way is more natural, and it doesn’t need as many changes
to Test::Unit – test_foo is just a regular test. And it’s just one
line longer. :slight_smile:

Yes, this is probably the way to go.

Related to this is also paper 2 in my thesis (available at
Data- och informationsteknik) which
evolves tests by combining test cells (basically Generators above)
into more complex test behaviors. I am currently working on extending
these ideas.

Wow! Most of that is way over my head, but it seems extraordinarily
cool. Using biological ideas in computing always struck me as a
beautiful idea. I’m glad we have guys like you on the Ruby boat. :slight_smile:

Thanks! I also think it’s pretty cool… :slight_smile:

RickCheck used to do special treatment of `border cases’; for example,
Integer had this definition:

class Integer
def border_cases
[1, 0, -1]
end
end

But I removed it for some reason that I seem to have forgotten. I’d
like to add it back in, but I’m not sure what’s the best way to do it.
But it’s certainly a Good Thing to test the border cases as soon as
possible; they’re the most likely to screw up, after all.

Boundary-Value Analysis is the common term yes.

Another thing I’d like to add is manual seeding of the random number
generator. Every test run could tell you what it was seeded with, so
that you can easily reproduce it later.

Yep, good idea.

Regards,

Robert