I strongly recommend using assert_raises
and assert_raises_regexp
from nose.tools
, which duplicate the behavior of assertRaises
and assertRaisesRegexp
from unittest.TestCase
. These allow using the same functionality as provided by unittest.TestCase
in test suites that do not actually use the unittest.TestCase
class.
I find that @raises
is much too blunt an instrument. Here is code illustrating the problem:
from nose.tools import *
something = ["aaa", "bbb"]
def foo(x, source=None):
if source is None:
source = something
return source[x]
# This is fine
@raises(IndexError)
def test1():
foo(3)
# This is fine. The expected error does not happen because we made
# a mistake in the test or in the code. The failure indicates we made
# a mistake.
@raises(IndexError)
def test2():
foo(1)
# This passes for the wrong reasons.
@raises(IndexError)
def test3():
source = something[2] # This is the line that raises the exception.
foo(10, source) # This is not tested.
# When we use assert_raises, we can isolate the line where we expect
# the failure. This causes an error due to the exception raised in
# the first line of the function.
def test4():
source = something[2]
with assert_raises(IndexError):
foo(10, source)
test3
passes, but not because foo
has raised the exception we were expecting but because the code that sets up the data to be used by foo
fails with the same exception. test4
shows how the test can be written using assert_raises
to actually test what we mean to be testing. The problem on the first line will cause Nose to report an error and then we can rewrite the test so that that line so that we can finally test what we did mean to test.
@raises
does not allow testing the message associated with the exception. When I raise ValueError
, just to take one example, I usually want to raise it with an informative message. Here's an example:
def bar(arg):
if arg: # This is incorrect code.
raise ValueError("arg should be higher than 3")
if arg >= 10:
raise ValueError("arg should be less than 10")
# We don't know which of the possible `raise` statements was reached.
@raises(ValueError)
def test5():
bar(10)
# Yes, we're getting an exception but with the wrong value: bug found!
def test6():
with assert_raises_regexp(ValueError, "arg should be less than 10"):
bar(10)
test5
which uses @raises
will pass, but it will pass for the wrong reason. test6
performs a finer test which reveals that the ValueError
raised was not the one we wanted.