I'd like to have a metaclass for subclasses of unittest.TestCase
, which will cause all methods to log when they're starting and finishing, including e.g., the @classmethod
s setUp
and tearDown
and their variants.
Unfortunately, the unittest
framework is very particular about the names of methods, and modifying them causes the framework not to find them. Using the answer to this question, I have something like the following:
class _UTMeta(type):
def __new__(cls, name, bases, dict_):
new_dict = {}
for fn_name, fn in dict_.items():
if fn_name.startswith('test_'):
new_dict[fn_name] = lambda self: _UTMeta._wrapped_test_function(self, fn_name, fn)
new_dict[fn_name].__name__ = fn_name # (**)
else:
new_dict[fn_name] = fn
return type.__new__(cls, name, bases, new_dict)
@staticmethod
def _wrapped_test_function(self, name, fn):
print 'starting', name
_test_logger.info('starting ' + name)
print 'finished', name
Note the line with the comment # (**)
- without it, unittest
doesn't recognize the methods as test methods.
This works for the regular test_*
methods, but not for the classmethod
s. To illustrate the problem, I'll use the classmethod setUpClass
. If I try to do exactly the same as the above, i.e., add
if fn_name == 'setUpClass':
new_dict[fn_name] = lambda self: _UTMeta._wrapped_test_function(self, fn_name, fn)
new_dict[fn_name].__name__ = fn_name
then I get:
ERROR: setUpClass (__main__._VersionDataTest)
TypeError: unbound method setUpClass() must be called with _VersionDataTest instance as first argument (got nothing instead)
However, if I try to properly add a dynamic classmethod
if fn_name == 'setUpClass':
new_dict[fn_name] = classmethod(lambda self: _UTMeta._wrapped_test_function(self, fn_name, fn))
new_dict[fn_name].__name__ = fn_name
I get
AttributeError: 'classmethod' object has no attribute '__name__'
Finally, if I omit the .__name__
manipulation, unittest
doesn't call this fixture method at all.
(Note this question asks something similar, but the accepted answer assumed that these methods are not classmethods, and so is completely wrong IMHO.)