2011-05-09 93 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