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.