Is there a standard "assert" idiom in Ruby?

  • Thread starter Kenneth McDonald
  • Start date
K

Kenneth McDonald

If you saw an earlier version of this question, apologies...I haven't
seen it, so assume I accidentally deleted it or it got lost or some such.

In other languages, I'm accustomed to using "assert" as a sort of
enforceable documentation. Is there an equivalent statement or idiom in
Ruby? I find that simply raising exceptions isn't quite right;
exceptions are normally used when one thinks something might be done
about a problem, whereas asserts are used to check that something is the
way it always should be. An 'assert' makes this point in the code. On a
lazier note, an 'assert' also needs less typing :).

I know I could define an assert myself, but something like that should
be universally, easily understood, not custom.

So, is there such an idiom that people use in place of an 'assert'
statement?


Thanks,
Ken
 
G

Gregory Brown

If you saw an earlier version of this question, apologies...I haven't
seen it, so assume I accidentally deleted it or it got lost or some such.

In other languages, I'm accustomed to using "assert" as a sort of
enforceable documentation. Is there an equivalent statement or idiom in
Ruby? I find that simply raising exceptions isn't quite right;
exceptions are normally used when one thinks something might be done
about a problem, whereas asserts are used to check that something is the
way it always should be. An 'assert' makes this point in the code. On a
lazier note, an 'assert' also needs less typing :).

Because Ruby's objects (and thus, their methods) are always open for
modification, this kind of mechanism is less helpful in Ruby than it
might be in other languages. There are design by contract frameworks
out there that do what you're looking for, but this often strikes most
seasoned Rubyists as being an imported practice from other languages,
rather than something that's really needed in Ruby.

I don't find exception raising to necessarily indicate that something
might be done about a problem, but it's not a bad thing that they can
easily be caught. Can you think of a reason why you wouldn't want a
user to possibly handle a failed assertion?

I think that if you describe the kinds of problems you're trying to
solve with assert, folks here will be able to best recommend the
idiomatic approach to handling them. It's possible that the kind of
work you're doing really needs this kind of behaviour, and then you
would need to either find a suitable third party library or roll your
own code. However, it's a little more likely that you're seeking a
solution to a problem that doesn't really exist in Ruby.

-greg
 
T

Thomas Wieczorek

2007/8/2 said:
If you saw an earlier version of this question, apologies...I haven't
seen it, so assume I accidentally deleted it or it got lost or some such.

In other languages, I'm accustomed to using "assert" as a sort of
enforceable documentation. Is there an equivalent statement or idiom in
Ruby? I find that simply raising exceptions isn't quite right;
exceptions are normally used when one thinks something might be done
about a problem, whereas asserts are used to check that something is the
way it always should be. An 'assert' makes this point in the code. On a
lazier note, an 'assert' also needs less typing :).

I know I could define an assert myself, but something like that should
be universally, easily understood, not custom.

I am not sure if you mean Unit Testing or Design by Contract. Ruby has
a Unit Testing Library Test::Unit with many assertions. You can find
it the documentation here
http://www.ruby-doc.org/stdlib/libdoc/test/unit/rdoc/
 
K

Kenneth McDonald

I'm not talking anything quite so complex as design by contract, just
the difference between

if x != y
...raise new exception...
end

vs.

assert x == y

(which, in Python, raises an AssertionError, actually). It's nice to
have a really fast, identifiable way of saying in code, "this (possibly
nonobvious) condition is expected to hold at this point," and is (for
me) as much about documenting twiddly bits of code as it is about error
checking. I was surprised Ruby doesn't offer this, and hoped there'd be
a bit if syntax or semantics I'd missed that took its place. Not a big
deal though.

Thanks,
Ken
 
M

Michael Fellinger

I'm not talking anything quite so complex as design by contract, just
the difference between

if x != y
...raise new exception...
end

vs.

assert x == y

(which, in Python, raises an AssertionError, actually). It's nice to
have a really fast, identifiable way of saying in code, "this (possibly
nonobvious) condition is expected to hold at this point," and is (for
me) as much about documenting twiddly bits of code as it is about error
checking. I was surprised Ruby doesn't offer this, and hoped there'd be
a bit if syntax or semantics I'd missed that took its place. Not a big
deal though.

Thanks,
Ken

# Though, i have to admit, i never had any need for this kind of
check, here it is:

class AssertionError < StandardError
end

def assert a, b
raise AssertionError, "#{a.inspect} != #{b.inspect}" unless a == b
end

assert 1, 1
assert 1, 2
 
D

Dan Zwell

Michael said:
class AssertionError < StandardError
end

def assert a, b
raise AssertionError, "#{a.inspect} != #{b.inspect}" unless a == b
end

assert 1, 1
assert 1, 2

I like this idea, but I would make it more general (so you could use it
for more than equality tests):

class AssertionError < StandardError
end

def assert(message=nil, &block)
unless(block.call)
raise AssertionError, (message || "Assertion failed")
end
end

Then you can do:AssertionError: This test will fail
from (irb):24:in `assert'
from (irb):29=> nil

And AssertionError will cause an exit (which is desirable behavior),
unless it is caught.

Dan
 
T

Thomas Wieczorek

2007/8/2 said:
I'm not talking anything quite so complex as design by contract, just
the difference between

if x != y
...raise new exception...
end

vs.

assert x == y

I am quite sure you're talking about Unit Testing. The PickAxe book
has a good and concise chapter(chapter 12) about it.

<code>
roman.rb
class Roman
MAX_ROMAN = 4999

def initialize(value)
if value <= 0 or value > MAX_ROMAN then
fail "Roman values must be > 0 and <= #{MAX_ROMAN}"
end
@value = value
end

FACTORS = [["m", 1000], ["cm", 900], ["d", 500],
["cd", 400], ["c", 100], ["xc", 90],
["l", 50], ["xl", 40], ["c", 10],
["ix", 9], ["v", 5], ["iv", 4],
["i", 1]]

def to_s
value = @value
roman = ""
for code, factor in FACTORS
count, value = value.divmod(factor)
roman << code unless count.zero?
end
roman
end
end

test_roman.rb
require 'roman'
require 'test/unit'

class TestRoman < Test::Unit::TestCase
def test_simple
assert_equal("i", Roman.new(1).to_s)
#will throw a failure
#assert_equal("xi", Roman.new(9).to_s)
assert_equal("ix", Roman.new(9).to_s)
assert_raise(RuntimeError) { Roman.new(5000) }
end
end
</code>

http://www.ruby-doc.org/stdlib/libdoc/test/unit/rdoc/classes/Test/Unit/Assertions.html
lists all available assert_* methods.
 
T

Tom Link

Then you can do:
AssertionError: This test will fail
from (irb):24:in `assert'
from (irb):29
=> nil

If implemented this way, assert comes with a potential speed penalty.
This is why such an assert feature usually implies a command line option
to ignore assertions in production mode (eg in Eiffel). In ruby this can
in theory be done by using the method_added hook or similar. The code
could then look like this

pre {|a, b| a > 0 && b > 0}
post {|r| r > 0}
def foo(a, b)
a + b
end

When a flag isn't set, pre & post could simply be ignored. There are
some libraries around that try to achieve this but IMHO they all have
some shortcoming. (Please correct me if I'm wrong.)

I think that a general way to define such wrappers had to be part of
ruby core language in order to get this right.

If ruby were lisp, you could define your own def_by_contract. But AFAIK
blocks cannot take optional arguments, which makes this currently
impossible (or not so useful).

Regards,
Thomas.
 
J

John Carter

If you saw an earlier version of this question, apologies...I haven't seen
it, so assume I accidentally deleted it or it got lost or some such.

In other languages, I'm accustomed to using "assert" as a sort of enforceable
documentation. Is there an equivalent statement or idiom in Ruby? I find that
simply raising exceptions isn't quite right; exceptions are normally used
when one thinks something might be done about a problem, whereas asserts are
used to check that something is the way it always should be.

I been around this particular block a few times and have come up with the simplest...

def foo( a)
raise "ASSERTION FAILURE! a '#{a}' not nice!" unless
a.nice?

end

Note a couple of other things...

I have thought about creating an
class AssertionFailure < Exception
end

But I'm not sure I care to. By an assertion failing I'm saying
"Something untoward/unclean has happened. I know not what, I have just
spotted the symptom. Give me some info, give me a backtrace, and I'll
sort it out later. The program has gone barking mad, take it out back
and shoot it"

Variants on the above, I may change the ASSERTION FAILURE to
PRECONDITION is that's what I mean...

def foo( a)
raise "PRECONDITION FAILURE! a '#{a}' not nice!" unless
a.nice?

end


Or I may let ruby do it's thing so instead of

def foo( a)
raise "PRECONDITION FAILURE! a '#{a}' is nil!" if a.nil?
a.doStuff()
end

I prefer...

def foo( a)
a.doStuff()
end

And let Ruby throw the appropriate exception (which gives me a back trace any way.)

Note I _never_ do something like...

x = ARGV[0].to_f
raise "ASSERTION FAILURE: x '#{x}' is negative" if x < 0

puts x ** 0.5

An assertion failure _always_ means I have may a mistake, _never_ the user.

In fact some of my scripts have at the end...

rescue Exception => details
puts "This is John's Fault. Call John now and tell him to fix it.:
puts details.to_s
...



Speedwise I profile, and if one of my asserts show up in a hot
spot... A # or two later it is gone from the execution path, but is
still there as documentation.

Another approach is to try lift the assert up the call tree. Usually
you escape some loop.

def foo(a)
@n.times {|s| bah(s,a)
end

def bah(s,a)
raise "ASSERT FAILURE!" unless a.nice?

# do things with s and nice a
end

becomes...

def foo(a)
raise "ASSERT FAILURE!" unless a.nice?
@n.times {|s| bah(s,a)
end

def bah(s,a)
# raise "ASSERT FAILURE!" unless a.nice?

# do things with s and nice a
end

John Carter Phone : (64)(3) 358 6639
Tait Electronics Fax : (64)(3) 359 4632
PO Box 1645 Christchurch Email : (e-mail address removed)
New Zealand
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,999
Messages
2,570,243
Members
46,835
Latest member
lila30

Latest Threads

Top