2

For unit tests (using the unittest module) that use the App Engine testbed, I need setUp and tearDown methods to activate and deactivate the testbed, respectively (slightly simplified):

class SomeTest(unittest.TestCase):

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

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

  def testSomething(self):
    ...

This quickly becomes a burden to write. I could write a base class TestCaseWithTestbed, but then I'd have to remember to call the superclass method each time I need a custom setUp in one of the test cases.

I thought it would be more elegant to solve this with a class decorator instead. So I'd like to write:

@WithTestbed
class SomeTest(unittest.TestCase):

  def testSomething(self):
    ...

With this decorator applied, the testbed should just be activated magically. So... how to implement the WithTestbed decorator? I currently have the following:

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

This works for simple cases, but has some serious problems:

  • The name of the test class becomes ClsWithTestbed and this shows up in the test output.
  • Concrete test classes calling super(SomeTestClass, self).setUp() end up in an infinite recursion, because SomeTestClass is now equal to WithTestbed.

I'm a bit hazy on Python's runtime type manipulation. So, how to do this the Right Way?

Thomas
  • 174,939
  • 50
  • 355
  • 478
  • 1
    Aren't you just inheriting `setUp`? What's wrong with ordinary subclassing? Why mess with a decorator for that? – S.Lott Jan 12 '12 at 15:45
  • If I have other setup tasks to do in specific test cases, I'd have to remember to call `super(SomeTestCase, self).setUp()`. That's ugly. – Thomas Jan 12 '12 at 15:48
  • 3
    It may be ugly, but it's the standard, common, normal, Pythonic approach. – S.Lott Jan 12 '12 at 15:49
  • It's not common to have to do this in `TestCase`s, though. It's easy to forget, because I just did :) – Thomas Jan 12 '12 at 15:50
  • 1
    It's pretty common in my projects. We have many TestCases with similar initial configurations. It may be easy to forget, but a common setup is a Great Big Deal and having proper subclasses helps organize that. That's why the `unittest` framework was -- explicitly -- made a proper class hierarchy in the native language. So you can use the language (not a silly DSL or something else) to organize your testing. Simple inheritance is a very powerful tool. – S.Lott Jan 12 '12 at 16:00
  • I think this is pretty common, it is referred to as a test "fixture". You might prefer to use `ParentClass.setUp(self)` rather than super, since that will allow you to call each parents' `setUp` if you've got multiple fixtures defined. – Robert Kluin Jan 12 '12 at 16:45
  • Subclassing is definitely the way to do this. You should be calling parent methods any time you override one anyway - it's not particularly onerous. – Nick Johnson Jan 13 '12 at 00:07

3 Answers3

2

This appears to work and solve the problems:

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

Does anyone see any problems with this approach?

Thomas
  • 174,939
  • 50
  • 355
  • 478
  • 1
    1) It obscures a proper class relationship with a decorator. 2) It makes ordinary subclass extensions nearly impossible to understand because it obscures simple inheritance. – S.Lott Jan 12 '12 at 17:43
  • @S.Lott In a library I use, there is a class 'A' with subclasses `B1`, `B2`; in my application, `C1` inherits `B1` and `C2` inherits `B2`, both of them need to override a method defined in `A`. In this case, a class decorator helps me reduce duplication. – satoru Jan 10 '14 at 03:15
1

Here's a simple way to do what you're asking with subclassing instead of a decorator:

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"

All you have to do is define mySetUp and myTearDown in your test cases instead of setUp and tearDown.

Duncan
  • 92,073
  • 11
  • 122
  • 156
  • Oooooh, I like this one. All the simplicity of inheritance without having to remember to call the superclass methods from the subclass ones. – robru Jun 24 '12 at 20:12
1

Something like this would work:

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):
    ...
philofinfinitejest
  • 3,987
  • 1
  • 24
  • 22
  • This will work in Python 3, where unbound methods are ordinary functions. In Python 2.x, you need to create method wrappers for the added functions. – kindall Jan 12 '12 at 16:45
  • That would be the case if a method were being dynamically added to an instance, but here it is being added to a class which makes all the difference. Running the code you can verify that the methods are in fact bound. Here is a good post on the subject: http://stackoverflow.com/a/962966/224295 – philofinfinitejest Jan 12 '12 at 17:16