使用类装饰器,如何在不重新定义类的情况下覆盖方法?

时间:2012-01-12 15:41:26

标签: python google-app-engine decorator python-2.7

对于使用App Engine testbed的单元测试(使用unittest模块),我需要setUptearDown方法分别激活和停用测试平台(略微简化) ):

class SomeTest(unittest.TestCase):

  def setUp(self):
    self.testbed = testbed.Testbed()
    self.testbed.activate()

  def tearDown(self):
    self.testbed.deactivate()

  def testSomething(self):
    ...

这很快成为写作的负担。我可以写一个基类TestCaseWithTestbed,但是每次我需要在其中一个测试用例中使用自定义setUp时,我必须记得调用超类方法。

我认为用类装饰器解决这个问题会更优雅。所以我想写一下:

@WithTestbed
class SomeTest(unittest.TestCase):

  def testSomething(self):
    ...

应用这个装饰器后,应该神奇地激活测试台。那么......如何实现WithTestbed装饰器?我目前有以下内容:

def WithTestbed(cls):
  class ClsWithTestbed(cls):

    def setUp(self):
      self.testbed = testbed.Testbed()
      self.testbed.activate()
      cls.setUp(self)

    def tearDown(self):
      cls.tearDown(self)
      self.testbed.deactivate()

  return ClsWithTestbed

这适用于简单的情况,但有一些严重的问题:

  • 测试类的名称变为ClsWithTestbed,并显示在测试输出中。
  • 调用super(SomeTestClass, self).setUp()的具体测试类最终会进行无限递归,因为SomeTestClass现在等于WithTestbed

我对Python的运行时类型操作有点模糊。那么,如何以正确的方式做到这一点?

3 个答案:

答案 0 :(得分:2)

这似乎可以解决问题:

def WithTestbed(cls):
  def DoNothing(self):
    pass

  orig_setUp = getattr(cls, 'setUp', DoNothing)
  orig_tearDown = getattr(cls, 'tearDown', DoNothing)

  def setUp(self):
    self.testbed = testbed.Testbed()
    self.testbed.activate()
    orig_setUp(self)
  def tearDown(self):
    orig_tearDown(self)
    self.testbed.deactivate()

  cls.setUp = setUp
  cls.tearDown = tearDown
  return cls

有没有人发现这种方法存在任何问题?

答案 1 :(得分:1)

这是一个简单的方法,可以用子类而不是装饰器来做你想要的事情:

class TestCaseWithTestBed(unittest.TestCase):

  def setUp(self):
    self.testbed = testbed.Testbed()
    self.testbed.activate()
    self.mySetUp()

  def tearDown(self):
    self.myTearDown()
    self.testbed.deactivate()

  def mySetUp(self): pass
  def myTearDown(self): pass

class SomeTest(TestCaseWithTestBed):
  def mySetUp(self):
    "Insert custom setup here"

您只需在测试用例中定义mySetUpmyTearDown,而不是setUptearDown

答案 2 :(得分:1)

这样的事情会起作用:

def WithTestbed(cls):
    cls._post_testbed_setUp = getattr(cls, 'setUp', lambda self : None)
    cls._post_testbed_tearDown = getattr(cls, 'tearDown', lambda self : None)

    def setUp(self):
        self.testbed = testbed.Testbed()
        self.testbed.activate()
        self._post_testbed_setUp()

    def tearDown(self):
        self.testbed.deactivate()
        self._post_testbed_tearDown()

    cls.setUp = setUp
    cls.tearDown = tearDown
    return cls

@WithTestbed
class SomeTest(object):
    ...