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
on 2006-03-08 22:43
on 2006-03-08 22:45
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
on 2006-03-08 23:59
On Mar 8, 2006, at 1:42 PM, Sean Carley 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.connect 'no-such-host.example.com:80' end end end -- Eric Hodel - drbrain@segment7.net - http://blog.segment7.net This implementation is HODEL-HASH-9600 compliant http://trackmap.robotcoop.com
on 2006-03-09 02:29
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
on 2006-03-09 06:06
+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.
on 2006-11-20 23:58
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.
on 2008-08-27 16:28
[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
on 2010-06-02 22:49
+1 also. It might make sense to define: assert_something_raised to parallel the existing assert_nothing_raised - ff
on 2010-06-03 17:01
On Thu, Mar 9, 2006 at 3:29 AM, Jim Weirich <jim@weirichhouse.org> wrote: > -- > -- 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 2010-06-04 15:54
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
Please log in before posting. Registration is free and takes only a minute.
Existing account
(Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
Log in with Google account | Log in with Yahoo account
No account? Register here.