35

I have a class:

class MyClass:
def __init__(self, foo):
    if foo != 1:
        raise Error("foo is not equal to 1!")

and a unit test that is supposed to make sure the incorrect arg passed to the constructor properly raises an error:

def testInsufficientArgs(self):
    foo = 0
    self.assertRaises((Error), myClass = MyClass(Error, foo))

But I get...

NameError: global name 'Error' is not defined

Why? Where should I be defining this Error object? I thought it was built-in as a default exception type, no?

Jerub
  • 41,746
  • 15
  • 73
  • 90
Sam McAfee
  • 10,057
  • 15
  • 60
  • 64

3 Answers3

35

'Error' in this example could be any exception object. I think perhaps you have read a code example that used it as a metasyntatic placeholder to mean, "The Appropriate Exception Class".

The baseclass of all exceptions is called 'Exception', and most of its subclasses are descriptive names of the type of error involved, such as 'OSError', 'ValueError', 'NameError', 'TypeError'.

In this case, the appropriate error is 'ValueError' (the value of foo was wrong, therefore a ValueError). I would recommend replacing 'Error' with 'ValueError' in your script.

Here is a complete version of the code you are trying to write, I'm duplicating everything because you have a weird keyword argument in your original example that you seem to be conflating with an assignment, and I'm using the 'failUnless' function name because that's the non-aliased name of the function:

class MyClass:
    def __init__(self, foo):
        if foo != 1:
            raise ValueError("foo is not equal to 1!")

import unittest
class TestFoo(unittest.TestCase):
    def testInsufficientArgs(self):
        foo = 0
        self.failUnlessRaises(ValueError, MyClass, foo)

if __name__ == '__main__':
    unittest.main()

The output is:

.
----------------------------------------------------------------------
Ran 1 test in 0.007s

OK

There is a flaw in the unit testing library 'unittest' that other unit testing frameworks fix. You'll note that it is impossible to gain access to the exception object from the calling context. If you want to fix this, you'll have to redefine that method in a subclass of UnitTest:

This is an example of it in use:

class TestFoo(unittest.TestCase):
    def failUnlessRaises(self, excClass, callableObj, *args, **kwargs):
        try:
            callableObj(*args, **kwargs)
        except excClass, excObj:
            return excObj # Actually return the exception object
        else:
            if hasattr(excClass,'__name__'): excName = excClass.__name__
            else: excName = str(excClass)
            raise self.failureException, "%s not raised" % excName

    def testInsufficientArgs(self):
        foo = 0
        excObj = self.failUnlessRaises(ValueError, MyClass, foo)
        self.failUnlessEqual(excObj[0], 'foo is not equal to 1!')

I have copied the failUnlessRaises function from unittest.py from python2.5 and modified it slightly.

Jerub
  • 41,746
  • 15
  • 73
  • 90
  • 1
    here's where you went wrong, here's what you wanted instead, and here's the code you intended to write. This is probably the best answer i've ever seen. – LoveMeSomeCode Feb 03 '13 at 03:03
  • 3
    self.failUnlessRaises is deprecated now. Use self.assertRaises instead. https://docs.python.org/2/library/unittest.html#deprecated-aliases – dgh Jun 12 '14 at 07:55
  • @LoveMeSomeCode `__init__` functions typically don't return any values. The typical `unittests` I see for functions, test the returned values by a function. Is it typically recommended to test your `__init__` functions as well? If so do you inspect the value that is changed inside the `__init__` function, by directly accessing the instance attributes of class that the `__init__` belongs to? – alpha_989 Jan 31 '18 at 18:57
  • @alpha_989 I'd say that in general I would test the __init__ functions if they had any destructive behavior or connection to an outside source, e.g. connecting to a database, opening a file, etc. Most of my classes will just initialize some variables there, so I don't bother. I'm pretty lazy though and typically just put tests on things that have broken in the past. – LoveMeSomeCode Feb 15 '18 at 15:07
7

How about this:

class MyClass:
    def __init__(self, foo):
        if foo != 1:
            raise Exception("foo is not equal to 1!")

import unittest

class Tests(unittest.TestCase):
    def testSufficientArgs(self):
        foo = 1
        MyClass(foo)

    def testInsufficientArgs(self):
        foo = 2
        self.assertRaises(Exception, MyClass, foo)

if __name__ == '__main__':
    unittest.main()
Terhorst
  • 2,071
  • 3
  • 16
  • 19
  • 1
    You can define 'class Error' as a subclass of Exception at the top and use it instead of Exception in this example, if you prefer. – Terhorst Sep 17 '08 at 22:39
1

I think you're thinking of Exceptions. Replace the word Error in your description with Exception and you should be good to go :-)

Jon Cage
  • 36,366
  • 38
  • 137
  • 215