【问题标题】:Using a class decorator, how to override a method without redefining the class?使用类装饰器,如何在不重新定义类的情况下覆盖方法?
【发布时间】:2012-02-08 21:40:29
【问题描述】:

对于使用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 的运行时类型操作有点模糊。那么,如何以正确的方式做到这一点?

【问题讨论】:

  • 不就是继承setUp吗?普通的子类化有什么问题?为什么要为此搞砸装饰师?
  • 如果我在特定测试用例中还有其他设置任务要做,我必须记得致电super(SomeTestCase, self).setUp()。太丑了。
  • 它可能很难看,但它是标准的、常见的、正常的、Pythonic 的方法。
  • TestCases 中这样做并不常见。这很容易忘记,因为我刚刚做到了:)
  • 这在我的项目中很常见。我们有许多具有相似初始配置的测试用例。这可能很容易忘记,但一个常见的设置是一件大事,拥有适当的子类有助于组织它。这就是为什么unittest 框架——明确地——在本地语言中建立了适当的类层次结构。因此,您可以使用该语言(不是愚蠢的 DSL 或其他东西)来组织您的测试。简单继承是一个非常强大的工具。

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


【解决方案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):
    ...

【讨论】:

  • 这将适用于 Python 3,其中未绑定的方法是普通函数。在 Python 2.x 中,您需要为添加的函数创建方法包装器。
  • 如果一个方法被动态添加到一个实例中,就会出现这种情况,但在这里它被添加到一个类中,这一切都不同了。运行代码,您可以验证这些方法实际上是绑定的。这是一个关于这个主题的好帖子:stackoverflow.com/a/962966/224295
【解决方案2】:

这是一种简单的方法,可以使用子类而不是装饰器来完成您的要求:

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

【讨论】:

  • 哇哦,我喜欢这个。继承的所有简单性,无需记住从子类调用超类方法。
【解决方案3】:

这似乎可以解决问题:

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) 它模糊了与装饰器的正确类关系。 2) 它使普通的子类扩展几乎无法理解,因为它掩盖了简单的继承。
  • @S.Lott 在我使用的库中,有一个带有子类B1B2 的“A”类;在我的应用程序中,C1 继承B1C2 继承B2,它们都需要覆盖A 中定义的方法。在这种情况下,类装饰器可以帮助我减少重复。
猜你喜欢
  • 2019-06-18
  • 2023-02-07
  • 2015-09-10
  • 2013-04-18
  • 2021-10-04
  • 2017-01-31
  • 2018-04-17
  • 2011-09-17
  • 1970-01-01
相关资源
最近更新 更多