2016-04-29 49 views
3

我想为因为我不想单元,使用自定义pyodbc数据库连接惩戒Django的单元pyodbc模块调用测试

views.py

from django.http import JsonResponse, HttpResponseNotFound, HttpResponseBadRequest, HttpResponseServerError, HttpResponseForbidden 
from django.core.exceptions import SuspiciousOperation 
from django.utils.datastructures import MultiValueDictKeyError 
import os 
import pyodbc 

# Create your views here. 

db_credentials = os.environ.get('DATABASE_CREDENTIALS') 
dbh = pyodbc.connect(db_credentials) 

def get_domains(request): 
    if request.method == 'GET': 
     args = request.GET 
    elif request.method == 'POST': 
     args = request.POST 

    try: 
     cursor = dbh.cursor() 
     if 'owner' in args: 
      owner = args['owner'] 
      cursor.execute('{call GET_DOMAINS_FOR_OWNER(?)}', owner) 
     else: 
      cursor.execute('{call GET_DOMAINS()}') 
     result = cursor.fetchall() 
     if(result): 
      return JsonResponse([row[0] for row in result], safe=False) 
     else: 
      return JsonResponse([], safe=False) 
    except pyodbc.Error as e: 
     return HttpResponseServerError(e) 
    except SuspiciousOperation as e: 
     return HttpResponseForbidden(e) 

一些Django的意见单元测试测试将进入数据库,我怎么能嘲笑的行为因为:

  • 因为pyodbc的模拟库将无法正常工作是一个Python C扩展
  • 使用sys.modules中似乎并没有工作,可能是因为该模块在views.py被使用,而不是tests.py

这里是我的测试车手

tests.py

from django.test import SimpleTestCase 
from sms_admin import * 

# Create your tests here. 


HTTP_OK = 200 
HTTP_NOTFOUND = 404 


class AdminTestCase(SimpleTestCase): 
    """docstring for AdminTestCase""" 

    def test_get_pool_for_lds(self): 
     response = self.client.get('/sms_admin/get_pool_for_lds', {'domain': 'sqlconnect', 'stage': 'dev', 'lds': 'reader'}) 
     self.assertEqual(response.content, b'pdss_reader') 
     self.assertEqual(response.status_code, HTTP_OK) 

回答

1

您可以修补pyodbc.connect没有任何限制,因为在后续的例子表明:

import pyodbc 
from unittest.mock import patch 

with patch("pyodbc.connect") as mock_connect: 
    pyodbc.connect("Credentials") 
    mock_connect.assert_called_with("Credentials") 

现在真正的问题在view.py是同时您导入view.py执行该线路的线路

dbh = pyodbc.connect(db_credentials) 

,你不能控制它没有实现某种黑客在你的测试代码像打补丁进口view.py或其他任何导入前连接它。

我想强烈地劝阻你写这种肮脏的技巧,并改变你的代码来实现一个懒惰的dbh属性。另一种方法可以编写自己的db class wrapper(更好),并将其修补到测试中,但这是一个强大的设计更改,您可以稍后通过实施测试的功能来引入它。

view.py使用:

_dbh = None 
def get_db(): 
    global _dbh 
    if _dbh is None: 
     _dbh = pyodbc.connect(db_credentials) 
    return _dbh 

其中cusror成为

cursor = get_db().cursor() 

现在你可以修补get_db()并在测试使用return_value模拟

class AdminTestCase(SimpleTestCase): 
    """docstring for AdminTestCase""" 

    def setUp(self): 
     super().setUp() 
     p = patch("yourpackage.view.get_db") 
     self.addCleanup(p.stop) 
     self.get_db_mock = p.start() 
     self.db_mock = self.get_db_mock.return_value 
     self.cursor_mock = self.db_mock.cursor.return_value 

    def test_get_pool_for_lds(self, get_db_mock): 
     .... configure self.cursor_mock to behave as you need 

     response = self.client.get('/sms_admin/get_pool_for_lds', {'domain': 'sqlconnect', 'stage': 'dev', 'lds': 'reader'}) 
     self.assertEqual(response.content, b'pdss_reader') 
     self.assertEqual(response.status_code, HTTP_OK) 

我离开的细节如何mock_cursor应该表现出来,并且游标调用声明。你可以通过阅读mock framework documentation来写。我以前用setUp()方法修补连接,因为我可以猜测,在几乎所有的测试中都需要它,其中cursor_mockdb_mockget_db_mock可以用于不同的行为:我的经验是,这种方法在你会增加更多的测试。

+0

在这种情况下,db_mock应该是另一个Mock对象吗? – tiagovrtr

+0

db_mock是另一种模拟,它与修补程序get_db在测试环境中的返回值相同 –

+0

@tiagovrtr我试图让我的答案更清晰。 –