2011-03-01 68 views
10

我正在使用python mock library開始測試。我想模擬一個在被測模塊的命名空間中導入的模塊,而不需要實際導入它或要求它首先存在(即拋出一個ImportError)。Python:模擬一個模塊,無需導入或需要它存在

假設下面的代碼存在:

foo.py

import helpers 
def foo_func(): 
    return helpers.helper_func() 

的目標是測試foo_func()的時候 'helpers.py' 不存在任何地方,如果它確實存在,行爲就好像它沒有。

首先嚐試使用超爽@patch裝飾:

from mock import patch, sentinel 
import foo 
@patch("foo.helpers") 
def foo_test(mock): 
    mock.helper_func.return_value = sentinel.foobar 
    assert foo.foo_func() == sentinel.foobar 

這工作如果「助手」模塊可以導入。如果它不存在,我會得到一個ImportError。

補丁,SANS裝飾下一步嘗試:

from mock import patch, sentinel, Mock 
import foo 
helpers_mock = patch("foo.helpers") 
helpers_mock.start() 

def foo_test(): 
    helpers_mock.helper_func = Mock('helper_func') 
    helpers_mock.helper_func.return_value = sentinel.foobar 
    assert foo.foo_func() == sentinel.foobar 

同樣,這不一樣,如果「助手」缺少工作...,如果它存在,則斷言失敗。不知道爲什麼會發生。

第三次嘗試,當前的解決方案:

import sys 
helpers_mock = Mock(name="helpers_mock", spec=['helper_func']) 
helpers_mock.__name__ = 'helpers' 
sys.modules['helpers'] = helpers_mock 
import foo 
def foo_test(): 
    helpers_mock.helper_func.return_value = sentinel.foobar 
    assert foo.foo_func() == sentinel.foobar 

此測試通過不管是否 「helpers.py」 是否存在。

這是實現此目標的最佳方式嗎?我正在使用的模擬庫提供了一個替代方案嗎?我可以通過其他方式來做到這一點?

+0

雖然我都是單元測試,但我認爲在這種情況下,您正在測試該語言的一項功能,而不是您的代碼的有用功能。你也可以在foo.py中修改你的導入,這樣一來這個猴子補丁對於一個進口警衛來說是不必要的(例如:try:除了ImportError:helpers = None之外的導入助手,而不是foo.py而不是裸導入)。我的意思是現在foo.py不是寫在一個允許幫助者失蹤,並允許這應該大大簡化您的測試。 – stderr 2011-03-01 06:15:52

+0

我真的很喜歡進口警衛的想法,它解決了很多問題。如果我按照你的建議使用進口警衛,我可以使用我給的第一個例子(@patch裝飾器)。謝謝! – 2011-03-01 07:02:28

回答

3

你有點失去了模擬的意義。當你想要一個具有特定接口的對象時,你應該創建它們,而不管它是如何實現的。

你正在做的是試圖重新實現python的模塊系統,再加上它是一個非常可怕的全局變量濫用引導。

不是讓foo創建一個模塊,而是創建一個Foo類,並在構造函數中傳遞helper。

class Foo(object): 
    def __init__(self, helpers): 
     self.helpers = helpers 

# then, instead of import foo: 
foo = Foo(mock_helpers) 

即使真正的「助手」實際上將是一個模塊,沒有任何理由,你需要與sys.modules中搞亂,並通過在您的測試import設置它。

如果富必須是一個模塊,那也沒關係,但你不喜歡這樣寫道:這樣的標準庫工作

# foo.py 
class Foo(object): 
    pass # same code as before, plus foo_func 

try: 
    import whatever 
    _singleton = Foo(whatever) 
except ImportError: 
    _singleton = Foo(something_else) 

def foo_func(): 
    return _singleton.foo_func() 

大塊。這幾乎是定義類似單例模​​塊的​​標準。

+1

這真的是很好的信息,不幸的是這意味着重寫很多代碼,這在目前是不實際的。但對於新項目,我一定會嘗試這種方法。 – 2011-03-03 06:48:15