2013-06-03 39 views
9

我有一個棘手的問題,我似乎無法掌握。我是 ,目前正在編寫一個django自定義auth-backend的單元測試。在我們的 系統中,我們實際上有兩個後端:一個是內置的django後端 以及將請求發送到基於Java的API 的自定義後端,該後端以XML形式返回用戶信息。現在,我正在寫單元 測試,所以我不想在系統之外發送請求,如 ,我沒有試圖測試Java API,所以我的問題是我怎麼能 解決這個問題,並嘲笑以最穩健的方式提供副作用。乾淨地嘲笑Django Unittests的遠程服務器和API

我測試的功能是這樣的,其中URL 設置值只是針對 驗證用戶名和密碼的數據,並返回XML的Java服務器的基本URL,以及服務價值是 只是一些魔法建設的網址查詢,它不重要的 我們:

@staticmethod 
def get_info_from_api_with_un_pw(username, password, service=12345): 
    url = settings.AUTHENTICATE_URL_VIA_PASSWORD 
    if AUTH_FIELD == "username": 
     params = {"nick": username, "password": password} 
    elif AUTH_FIELD == "email": 
     params = {"email": username, "password": password} 
    params["service"] = service 
    encoded_params = urlencode([(k, smart_str(v, "latin1")) for k, v in params.items()]) 
    try: 
     # get the user's data from the api 
     xml = urlopen(url + encoded_params).read() 
     userinfo = dict((e.tag, smart_unicode(e.text, strings_only=True)) 
         for e in ET.fromstring(xml).getchildren()) 
     if "nil" in userinfo: 
      return userinfo 
     else: 
      return None 

所以,我們得到的XML,解析成一個字典,如果關鍵的零存在 那麼我們可以返回字典和發揚快樂並經過認證。 顯然,一個解決方案是隻是爲了找到猴補丁在XML變量的邏輯,我發現這個答案的方式以某種方式覆蓋或 :

How can one mock/stub python module like urllib

我試圖實現類似的東西,但在細節上有是 非常粗略,我似乎無法得到這個工作。

我也拍攝到的XML響應,並把它放在一個本地文件中 測試文件夾尋找一種方法來使用,作爲傳遞到測試功能的URL參數模擬 響應的意圖, 像這樣將覆蓋網址:

@override_settings(AUTHENTICATE_URL_VIA_PASSWORD=(os.path.join(os.path.dirname(__file__), "{0}".format("response.xml")))) 
def test_get_user_info_username(self): 
    self.backend = RemoteAuthBackend() 
    self.backend.get_info_from_api_with_un_pw("user", "pass") 

但也需要考慮到的是, 函數定義(即「URL + encoded_pa​​rams」)的URL建築邏輯。再次,我可以將 的響應文件重命名爲與連接的url相同,但這不像是一個很好的函數單元測試和更多的「作弊」,整個 事情越來越多這些解決方案一直都很脆弱,無論如何它只是一個固定裝置,這也是我想要儘量避免的東西。

我也想知道是否有一種方法可以在django開發服務器上爲xml服務,然後在那裏指向這個函數?這似乎是一個更爲理智的解決方案,但是如果這樣的事情是可能的或者明智的,那麼很多使用Google的人不會給我提供任何線索,即使如此,我也認爲這不是一種在開發環境之外運行的測試。

所以,理想情況下,我需要能夠以某種方式嘲笑「服務器」,以 採取了Java API的地方在函數調用,或以某種方式成爲 一些XML負載的功能,可以作爲其網址打開,或 monkeatch從測試本身的功能,或...

模擬庫是否有適當的工具來做這樣的事情?

http://www.voidspace.org.uk/python/mock

那麼,有兩點對這個問題1)我想解決一個乾淨的方式我 特別的問題,更重要的是2)什麼是 的最佳做法乾淨寫Django的單元 - 根據數據,Cookie等, 進行測試,以便從遠程的 API進行用戶驗證,該API位於您的域之外?

回答

1

如果使用得當,模擬庫應該可以正常工作。我更喜歡minimock庫,並且我編寫了一個小型基本單元測試用例(minimocktest),以幫助實現此目的。

如果你想融入這個測試用例使用Django測試urllib可以按如下做到這一點:

from minimocktest import MockTestCase 
from django.test import TestCase 
from django.test.client import Client 

class DjangoTestCase(TestCase, MockTestCase): 
    ''' 
    A TestCase class that combines minimocktest and django.test.TestCase 
    ''' 

    def _pre_setup(self): 
     MockTestCase.setUp(self) 
     TestCase._pre_setup(self) 
     # optional: shortcut client handle for quick testing 
     self.client = Client() 

    def _post_teardown(self): 
     TestCase._post_teardown(self) 
     MockTestCase.tearDown(self) 

現在你可以使用,而不是直接使用Django的測試用例這個測試用例:

class MySimpleTestCase(DjangoTestCase): 
    def setUp(self): 
     self.file = StringIO.StringIO('MiniMockTest') 
     self.file.close = self.Mock('file_close_function') 
    def test_urldump_dumpsContentProperly(self): 
     self.mock('urllib2.urlopen', returns=self.file) 
     self.assertEquals(urldump('http://pykler.github.com'), 'MiniMockTest') 
     self.assertSameTrace('\n'.join([ 
      "Called urllib2.urlopen('http://pykler.github.com')", 
      "Called file_close_function()", 
     ])) 
     urllib2.urlopen('anything') 
     self.mock('urllib2.urlopen', returns=self.file, tracker=None) 
     urllib2.urlopen('this is not tracked') 
     self.assertTrace("Called urllib2.urlopen('anything')") 
     self.assertTrace("Called urllib2.urlopen('this is mocked but not tracked')", includes=False) 
     self.assertSameTrace('\n'.join([ 
      "Called urllib2.urlopen('http://pykler.github.com')", 
      "Called file_close_function()", 
      "Called urllib2.urlopen('anything')", 
     ])) 
+0

在你的情況下,你會想要模擬以下內容:'import models; self.mock('models.urlopen')',因爲它似乎是在您的模型文件'urllib import urlopen'中將它導入如下。 – Pykler

+1

啊,那太棒了,看起來這樣的嘲笑就是票。非常感謝! – osman

0

以下是我最終用於記錄的解決方案的基礎知識。我最終使用了Mock庫本身而不是Mockito,但這個想法是一樣的:

from mock import patch 

@override_settings(AUTHENTICATE_LOGIN_FIELD="username") 
@patch("mymodule.auth_backend.urlopen") 
def test_get_user_info_username(self, urlopen_override): 
    response = "file://" + os.path.join(os.path.dirname(__file__), "{0}".format("response.xml")) 
    # mock patch replaces API call 
    urlopen_override.return_value = urlopen(response) 
    # call the patched object 
    userinfo = RemoteAuthBackend.get_info_from_api_with_un_pw("user", "pass") 
    assert_equal(type(userinfo), dict) 
    assert_equal(userinfo["nick"], "user") 
    assert_equal(userinfo["pass"], "pass")