2013-06-03 37 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")