2012-01-12 94 views
2

单元测试(使用unittest模块)使用App Engine testbed,我需要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的运行时类型操作有些朦胧。那么,如何以正确的方式做到这一点?

+1

你不只是继承'setUp'?普通子类有什么问题?为什么惹恼装饰者? – 2012-01-12 15:45:05

+0

如果我在特定的测试用例中有其他设置任务,我必须记得调用'super(SomeTestCase,self).setUp()'。这很丑陋。 – Thomas 2012-01-12 15:48:48

+3

它可能很难看,但它是标准的,普通的,正常的Pythonic方法。 – 2012-01-12 15:49:32

回答

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)它掩盖了与装饰者的适当的阶级关系。 2)它使普通的子类扩展几乎不可能理解,因为它掩盖了简单的继承。 – 2012-01-12 17:43:49

+0

@ S.Lott在我使用的库中,有一个类'A',子类为'B1','B2';在我的应用程序中,'C1'继承'B1','C2'继承'B2',它们都需要覆盖'A'中定义的方法。在这种情况下,类装饰器可以帮助我减少重复。 – satoru 2014-01-10 03:15:11

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

+0

Oooooh,我喜欢这个。所有简单的继承,而不必记得从子类中调用超类方法。 – robru 2012-06-24 20:12:06

0

像这样的工作:

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): 
    ... 
+0

这将在Python 3中工作,其中未绑定的方法是普通函数。在Python 2.x中,您需要为添加的函数创建方法包装器。 – kindall 2012-01-12 16:45:01

+0

如果一个方法被动态地添加到一个实例中,情况就会如此,但是在这里它将被添加到一个使所有区别变得非常重要的类中。运行代码,你可以验证这些方法实际上是绑定的。这是关于这个问题的好帖子:http://stackoverflow.com/a/962966/224295 – philofinfinitejest 2012-01-12 17:16:37