2011-05-09 99 views
6

我想编写一个Python装饰器来装饰unittest.TestCase的测试函数,以决定这个函数应该运行的目标主机。看到这个例子:带有参数的Python装饰器,多次运行函数?

class MyTestCase(unittest.TestCase): 
    @target_host(["host1.com", "host2.com"]) 
    def test_my_command(self): 
     #do something here against the target host 

在装饰功能,我希望能够执行对所有主机这个测试中,我该怎么做呢? target_host的声明应该返回一个新的函数,但可以返回测试运行器可以执行的多个函数吗?

谢谢!

回答

11

您只能返回一个对象(所以在技术上可以返回一组函数)。如果你想避免惊人的每个人,并且如果你想调用结果,你最好返回一个函数。但是这个函数很可能会在循环中调用其他几个函数......你知道这会导致什么吗?

您需要一个装饰器的工厂,它返回一个闭包,调用它们应用到的每个参数集的函数一次。在代码(包括functools.wraps保持名称和文档字符串,可能是有用与否,我倾向于在默认情况下将其列入):

def call_with_each(*arg_tuples): 
    def decorate(f): 
     @functools.wraps(f) 
     def decorator(): 
      for arg_tuple in arg_tuples: 
       f(*arg_tuple) 
     return decorator 
    return decorate 

# useage example: 
@call_with_each((3,), (2,)) # note that we pass several singleton tuples 
def f(x): 
    print x 
# calling f() prints "3\n2\n" 

支持关键字参数需要更多的代码也许有些丑陋,但是是可能的。如果它始终是一个参数,则代码可以简化(def call_with_each(*args)for arg in args: f(arg)等)。

+0

伟大的答案,但我发现基于类的装饰更容易使用我的工作n装饰者接受参数/战士的情况。 – zeekay 2011-05-09 18:58:59

+0

感谢您的想法。 – ycseattle 2011-05-09 21:34:45

+0

我和OP有相同的需求。但是你的建议并不适合我。我遇到的第一个问题是解决方案相当简单。装饰器需要应用于一个方法,但你的答案中的“装饰器”不需要“自我”参数。另一个问题似乎更成问题。测试运行者将在循环之外调用适当的设置和清理方法。这意味着状态不会在测试用例之间得到清理,就像使用两个单独的'test_'函数一样。这会导致测试用例失败。 – kasperd 2014-12-16 14:05:34

2

如果您希望装饰器接受参数,那么使用基于类的装饰器是一种方法。根据我的经验,他们最终能够更轻松地进行推理和维护。它们很简单,__init__接受装饰器的任何参数,__call__返回装饰函数。其中一个写装饰器的问题是如果它们传递参数或者不参与,它们的行为完全不同。这是很容易在基于类的装饰,以解决这个问题,让你的装饰,以接受参数或不:

class target: 
    def __init__(self, targets): 
     """Arguments for decorator""" 
     self.targets = None 
     if not hasattr(targets, '__call__'): 
      # check we are actually passed arguments! 
      # if targets has __call__ attr, we were called w/o arguments 
      self.targets = targets 

    def __call__(self, f): 
     """Returns decorated function""" 
     if self.targets: 
      def newf(*args, **kwargs): 
       for target in self.targets: 
        f(target) 
      return newf 
     else: 
      return f 

现在,如果我们使用参数的装饰,它会按预期工作,调用我们的函数3倍:

>>> @target([1,2,3]) 
..: def foo(x): print x 
... 

>>> foo() 
1 
2 
3 

但是,如果我们不带参数调用,我们将返回,而不是原来的功能:

>>> @target  
def foo(x): print x 
..: 

>>> foo(3) 
<<< 3 
+0

你可以用它来装饰类方法吗? – Randy 2015-07-09 17:31:31

+0

回答了我自己的问题 - 只是将'f'或'newf'的返回封装在一个包装中,并将包装返回到'__call__'中。必须将类方法的'self'重命名为不是装饰器类的'self'。颇有疑问。 – Randy 2015-07-09 17:44:42