this sunny sunday afternoon i was putting together some utility code(stupid me!) to do some remote blogging from the shell or the cosy inside of my vim editing session. This for some later time, but while i was going on with my test-driven/test first development i hit the problem of missing a test to check for the base class of my errors. I wanted to:
assert_raise(StandardError) { @blog.find_post(:postid => 123456789) }
to generally check for any kind of trouble bubbling up but it was not working as expected. Instead i got nasty Failure reports:
1) Failure:
test_find_post(XMLRPCTest) [mylib.rb:83]:
<StandardError> exception expected but was
Class: <XMLRPC::FaultException>
Message: <"Sorry, no such post.">
I suspected XMLRPCTest::FaultException
to maybe not beeing derived from StandardError, but that was not the case. A look in the ruby documentation and the ruby-talk thread confirmed:
assert_raise(*args) {|| ...}
is checking for the EXCACT exception type only!
How was it for you?
On the ruby-talk mailing list there was a little discussion about this topic and i pretty much agree with all the +1 sayers on the list. Edwin Fine propsed adding his own assert_raise_s
method the Assertions class. You easily get into muddy waters with opening standard ruby classes for some duck-typing but reverting to:
assert_raise(XMLRPC::FaultException) { @blog.find_post(:postid => 123456789) }
was not an option. This would expose way to much implementation detail to this very high level coding of the very first tests so early in the project. So I was using the source, as yoda said and found another solution for me.
Modules as base class arguments to assert_raises
After i read Edwin’s post i checked the source for def
assert_raise
and learned that this method is actually checking for some kind of exception base class. The argument to assert_raise
is an array of exception types. assert_raise
does partition this types into Class
and Module
.
The assertion_raise
checks for Class
types is exact, but the Module
is not(can’t be). They are checked with an is_a?
condition - there can’t be no object instance of module type or course.
My original test therefor simply fails because StandardError
is not a Module
but a class. The XMLRPC::FaultException
implementation is not mine but it is bubbling through my lib which i’m testing and this is precisly the condition i want to write tests for.
“Module tagged” exceptions and assert_raises_kind_of
First i wrote an empty tagging module for lib to tag all Errors and exceptions coming from my lib:
module MyLib
module Error; end
end
Now i can tag all exception from some deeper laying code with my Error module:
begin
...
rescue => e # errors bubbling from the underworld
# tag it
class << e; include MyLib::Error; end
# throw it
raise e
end
Voila, and now i can write:
assert_raise(BlogMist::Error) { @blog.find_post(:postid => 123456789) }
and finally got what i wanted but you’re milage may vary. Basically this gives me a way to create some kind of folksonomy of errors coming from my library. Don’t know yet where this might lead me, but hey, ruby is the best for protoyping and playing around!
Don’t be lazy!
Testing for error base classes instead of pricise error handling is not to make you lazy! As discussed on the forum thread it is to start with tests early on and being able to refine error condition testing over time.
Technorati tag: ruby, ducktyping, assert_raise, test-first,