Assert_raise question


#1

Currently, assert_raise expects you to know exactly what kind of
exception
is going
to be thrown by raise. Basically it tests to see if exception.class ==
expected.class.
I think a more appropriate behavior would be to test
exception.kind_of?expected. I
would also be happy if there were one method that performed the way it
does
currently
and another that checks exception parentage.

Please see the code below for an example of two tests. I think both
should
pass but
only the first test does because ArgumentError != StandardError.

Sean C.

Begin test.rb

require ‘test/unit’
class TestRaise < Test::Unit::TestCase
def test_raise
assert_raise(ArgumentError){raise ArgumentError.new(“basic argument
issue”)}
end
def test_raise_parent
assert_raise(StandardError){raise ArgumentError.new(“basic argument
issue”)}
end
end

End test.rb

ruby test.rb
Loaded suite test
Started
.F
Finished in 0.018356 seconds.

  1. Failure:
    test_raise_parent(TestRaise) [test.rb:7]:
    exception expected but was
    Class:
    Message: <“basic argument issue”>
    —Backtrace—
    test.rb:7:in test_raise_parent' test.rb:7:intest_raise_parent’

2 tests, 2 assertions, 1 failures, 0 errors


#2

Sean C. wrote:
[…]

I think a more appropriate behavior would be to test
exception.kind_of?expected. I would also be happy if
there were one method that performed the way it
does currently and another that checks exception parentage.

+1


– Jim W.


#3

On Mar 8, 2006, at 1:42 PM, Sean C. wrote:

Currently, assert_raise expects you to know exactly what kind of
exception
is going to be thrown by raise. Basically it tests to see if
exception.class ==
expected.class.

I think a more appropriate behavior would be to test
exception.kind_of?expected. I would also be happy if there were
one method that performed the way it does currently and another
that checks exception parentage.

For a given input a method should only raise one class of exception.
The restriction forces you to write better tests.

Please see the code below for an example of two tests. I think
both should
pass but only the first test does because ArgumentError !=
StandardError.

require ‘socket’
require ‘test/unit’

class MyClass
def connect(overthere)
unless overthere =~ /:\d+\Z/ then
raise ArgumentError, “Use host:port, got #{overthere}”
end
host, port = overthere.split ‘:’
@socket = TCPSocket.new host, port.to_i
end
end

This method can raise at least two different exceptions on bad input
so you should test for them separately. This allows consumers of
your code to behave properly depending upon what arguments they feed
your code.

class TestMyClass < Test::Unit::TestCase

def setup
@obj = MyClass.new
end

def test_connect_bad_args_no_port
assert_raises ArgumentError do
@obj.connect ‘host’
end
end

def test_connect_bad_args_bad_port
assert_raises ArgumentError do
@obj.connect ‘host:port’
end
end

def test_connect_bad_host
assert_raises SocketError do
@obj.connectno-such-host.example.com:80
end
end

end


Eric H. - removed_email_address@domain.invalid - http://blog.segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com


#4

Eric H. wrote:

For a given input a method should only raise one class of exception.
The restriction forces you to write better tests.

The reason I would like it is that the test is the specification of the
behavior. I often wish to specify that a method will raise a more
general exception than the one it actually raises. The actual exception
raised may be an implementation detail that I don’t wish to have
expressed in the spec.


– Jim W.


#5

+1 again.

A big part of UT is to allow ease of refactoring. Part of refactoring
is, IMO, making exceptions more specific (eg from raise “No record
found” to raise DBException, “No record found”) and the like.

Having to change your UT’s when you want to refactor is a bit self
defeating.


#6

unknown wrote:

+1 again.

A big part of UT is to allow ease of refactoring. Part of refactoring
is, IMO, making exceptions more specific (eg from raise “No record
found” to raise DBException, “No record found”) and the like.

Having to change your UT’s when you want to refactor is a bit self
defeating.

Agreed.

Another +1. The whole point of having an exception hierarchy is so that
you can specify a parent exception class at the interface level. You do
this so that as your software grows, you can add new sub-exceptions
without breaking your unit tests or your client’s code. Sometimes you
are interested in the actual exception, but sometimes you just want to
catch the more general exception and don’t care what the specific class
is.

Here’s an example:

begin
eval(some text)
rescue ScriptError => e

Handle the script error

end

Do we always care if it was a ScriptError descendant (shown below)?

LoadError
NotImplementedError
SyntaxError

No. Not always. Maybe not even in a unit test. Maybe the unit test
starts off more general, and then as time goes by gets refined to test
the exact exceptions.

I believe it’s better to allow the users of general library code to use
it the way that suits them best. So, I suggest adding another assertion
method, assert_raise_s, which will succeed if the argument class of the
raised exception is derived from (<=) the expected class. That way, you
can have both a strict assertion (assert_raise) and the more general
case (assert_raise_s).

In the meantime, I will just add a local assert_raise_s because, thanks
to Matz, Ruby classes are open to extension.


#7

+1 also.

It might make sense to define:
assert_something_raised
to parallel the existing
assert_nothing_raised

  • ff

#8

On Thu, Mar 9, 2006 at 3:29 AM, Jim W. removed_email_address@domain.invalid
wrote:


– Jim W.

There is no way you are wrong, thus you are both right :P.
Personally I prefer rspec’s behavior, also because it matches the
semantics of “rescue”.
But I would like to raise a question myself - I just broke the record
for the cheapest pun - why not have two different assertions for being
able to test/specify two different things.

Cheers
R.


#9

[snip]

Begin test.rb

require ‘test/unit’
class TestRaise < Test::Unit::TestCase
def test_raise
assert_raise(ArgumentError){raise ArgumentError.new(“basic argument
issue”)}
end
def test_raise_parent
assert_raise(StandardError){raise ArgumentError.new(“basic argument
issue”)}
end
end

End test.rb

[snip]

This is a somewhat unrelated question, but in playing around with this,
I discovered something odd. The test_raise method above works, but the
following modification of it does not:

def test_raise
assert_raise ArgumentError {raise ArgumentError.new(“basic argument
issue”)}
end

Now there’s suddenly an Exception: undefined method ‘ArgumentError’…
I was more than a little surprised to discover that the parens make a
difference here? Can anyone explain to me why there’s a difference?

Doug


#10

Doug G. wrote:

def test_raise
assert_raise ArgumentError {raise ArgumentError.new(“basic argument
issue”)}
end

Now there’s suddenly an Exception: undefined method ‘ArgumentError’…
I was more than a little surprised to discover that the parens make a
difference here? Can anyone explain to me why there’s a difference?

foo { … } is parsed as a method call with a block.

So your code is parsed as:

assert_raise(ArgumentError() { mode code })

Method names which start with a capital letter are fairly infrequent,
but are allowed - see for example Integer(x) or Array(x).

Using do…end instead of {…} tips the parsing the right way:

assert_raise ArgumentError do
raise ArgumentError.new “basic argument issue”
end