views:

189

answers:

3

I'm running into a strange problem when using unittest.assertRaises. When executing the code below I get the following output:

E
======================================================================
ERROR: testAssertRaises (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\home\python_test\src\derived.py", line 29, in testAssertRaises
    self.assertRaises(MyError, self.raiser.raiseMyError)
  File "C:\Programme\Python26\lib\unittest.py", line 336, in failUnlessRaises
    callableObj(*args, **kwargs)
  File "C:\home\python_test\src\derived.py", line 15, in raiseMyError
    raise MyError("My message")
MyError: 'My message'

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (errors=1)

The correct exception gets raised, but the test fails! If I'm catching the BaseError the test succeeds.

Somehow this seems to be a scope issue of unittest not being able to see the MyError exception class. Can someone explain that? Is there some workaround?

I am testing the following Python code which is an implementation for dynamically constructing objects by their class names.

This is the base module "bases.py":

class BaseClass(object):

    @staticmethod
    def get(className):
        module = __import__("derived", globals(), locals(), [className])
        theClass = getattr(module, className)
        return theClass()


class BaseError(Exception):

    def __init__(self, msg):
        self.msg = msg

    def __str__(self):
        return repr(self.msg)

This is the module to test, "derived.py":

import unittest

from bases import BaseError
from bases import BaseClass


class MyErrorRaiser(BaseClass):    

    def raiseMyError(self):
        raise MyError("My message")


class MyError(BaseError):
    '''
    '''


class Test(unittest.TestCase):

    def setUp(self):
        self.raiser = BaseClass.get("MyErrorRaiser")

    def testAssertRaises(self):
        self.assertRaises(MyError, self.raiser.raiseMyError)


if __name__ == "__main__":
    unittest.main()
A: 

The problem is probably that your BaseClass.get() method is returning you another class. By the way, that method is horrible in itself, I wonder why you're doing this.

Antoine P.
Yes, that is its purpose. This is just a simlified example of the complete code which is a kind of Abstract Factory for instantiating interface implementations by their name. Anyway, I would appreciate if you could help me with my problem or provide a better implementation and not just a rant.
desolat
"Just a rant"?! I pointed out that the problem is probably that `BaseClass.get()` returns you another class than the one you are passing to `assertRaises`. `assertRaises` is not at fault. Now if you don't like people making comments on your code or design style then please don't post programming questions on the Internet.
Antoine P.
If you would have ran the code above you would have seen that this is not the problem. If you don't get the problem, do not answer questions you do not understand. See the accepted answer for how to do it.
desolat
Actually, my description was accurate although less precise than the one you accepted. If you fail to grasp this (that `assertRaises` failed precisely because given a different class than the one raised in your code), I'm sorry for you but that doesn't make you look any better. Your original description of the problem (that `assertRaises` supposedly only catches the base exception) is completely wrong, so don't try to incriminate people who point that out to you.
Antoine P.
Antoine: couldn't help but notice what you said applies back: "If you don't like people making comments on your [answers] then please don't post programming [answers] on the Internet." :)
Roger Pate
Probably, but I am not the one asking for advice or looking for expertise here ;-)
Antoine P.
+1  A: 

When you run derived.py, it is run as the __main__ module (since you ran it directly rather than importing it). When you later import it explicitly, another copy of the module is created, this time under the name derived. So __main__.MyError is not the same as derived.MyError, and the exception isn't caught.

interjay
Ah, that makes sense. Any idea how to better implement that? I already noticed that it works if I move the exceptions to a seperate module.
desolat
Moving the exceptions will fix this issue, but you still have two different copies of the same module and there could be other problems later on. You need to somehow make sure that the main module will not be imported by another module. I don't know why you need the `BaseClass.get` method, so I can't tell you how to improve it, except to avoid calling it in the unit test.
interjay
+2  A: 

As mentioned, the issue is modules __main__ and derived are not one and the same; this answer is about how you fix that.

Don't mix module code and script code. Start to think of if __name__ == "__main__" code as a hack. (It's still very convenient at times and I use it often for debugging, etc., but view it as a hack so you always get a slight mental nudge writing it.) The new script that would then run everything would be simple (and never imported):

#!/usr/bin/env python
import derived
import sys
sys.exit(derived.main(sys.argv[1:]))

Or with various differences by personal preference. Just make sure you add derived.main to do what you want.

I've also seen another hack which is less common, at the top of derived.py:

import sys
if __name__ == "__main__":
  import derived # import the same module under its "correct" name
  sys.exit(derived.main(sys.argv[1:]))

While wasteful in parsing the same code twice, this is self-contained in exchange.

Roger Pate