Assert_raise question

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 Carley

### 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]:
<StandardError> exception expected but was
Class: <ArgumentError>
Message: <"basic argument issue">
---Backtrace---
test.rb:7:in `test_raise_parent'
test.rb:7:in `test_raise_parent'

···

---------------

2 tests, 2 assertions, 1 failures, 0 errors

Sean Carley 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 Weirich

--
Posted via http://www.ruby-forum.com/\.

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.connect 'no-such-host.example.com:80'
     end
   end

end

···

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

--
Eric Hodel - drbrain@segment7.net - http://blog.segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com

[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

···

--
Posted via http://www.ruby-forum.com/\.

Eric Hodel 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 Weirich

--
Posted via http://www.ruby-forum.com/\.

Doug Glidden 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

···

--
Posted via http://www.ruby-forum.com/\.

+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.

Jim Weirich wrote:

···

Eric Hodel 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 Weirich

--
Posted via http://www.ruby-forum.com/\.

Eric Hodel 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 Weirich

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.

···

On Thu, Mar 9, 2006 at 3:29 AM, Jim Weirich <jim@weirichhouse.org> wrote:

--
Posted via http://www.ruby-forum.com/\.

--
The best way to predict the future is to invent it.
-- Alan Kay

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.

···

--
Posted via http://www.ruby-forum.com/\.

+1 also.

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

- ff

···

--
Posted via http://www.ruby-forum.com/.