2

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 @classmethods 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 classmethods. 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.)

Community
  • 1
  • 1
Ami Tavory
  • 74,578
  • 11
  • 141
  • 185

0 Answers0