A friend of mine asked for my help to find the cause of a bug he was seeing in a complex Python class he was writing.
The class was supposed to raise an exception when a certain method was called but the unit tests were not picking up that exception. I have to confess I was a little stumped at first but simplifying his code to the bare minimum made the problem more evident.
Here’s an even more simplified version of his code just to illustrate the problem:
from unittest import TestCase class MyClass(): def my_attr(self): raise AttributeError def __getattr__(self, name): raise AttributeError class MyTest(TestCase): def test_my_attr(self): c = MyClass() self.assertRaises(AttributeError, c.my_attr) def test_getattr(self): c = MyClass() self.assertRaises(AttributeError, c.foo)
The problem was that
test_my_attr() was passing but
test_getattr() was failing, which at first may seem odd because both methods in
MyClass should raise an
AttributeError but only one of them seemed to do so.
The explanation is actually quite simple: in
c.my_attr is actually an attribute (a method, to be more specific) of
MyClass but in
c.foo is not a method of
c.__getattr__() should be executed instead, but in fact what happens is that the
c.foo attribute access is evaluated before
assertRaises() is executed, and thus the test never intercepts the exception.
So how do we deal with it? There are two ways to do it:
- With a context manager, so we can intercept the exception after
assertRaises()is set up;
- Using a lambda function to delay the attribute access evaluation.
# Context manager def test_getattr(self): c = MyClass() with self.assertRaises(AttributeError): c.foo() # Lambda function def test_getattr(self): c = MyClass() self.assertRaises(AttributeError, lambda: c.foo())
This is the type of thing that can trip up a novice Python developer and even more experienced ones, so here are a couple of solutions, for everyone’s enjoyment!
Do you know other ways of dealing with this scenario?