Используя декоратор класса, как переопределить метод, не переопределяя класс? - PullRequest
2 голосов
/ 12 января 2012

Для модульных тестов (с использованием модуля unittest), в которых используется тестовый стенд App Engine , мне нужны setUp и tearDown методы для активации и деактивации тестового стенда соответственно (слегка упрощенно)

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 ]

2 голосов
/ 12 января 2012

Похоже, что это работает и решает проблемы:

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 голос
/ 12 января 2012

Примерно так будет работать:

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):
    ...
1 голос
/ 12 января 2012

Вот простой способ сделать то, что вы просите, используя подклассы вместо декоратора:

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"

Все, что вам нужно сделать, это определить mySetUp и myTearDown в ваших тестовых случаях вместоsetUp и tearDown.

...