2016-04-29 2 views
3

Я хочу, чтобы сделать модульные тесты для некоторых взглядов Джанго, которые используют подключение пользовательских баз данных pyodbcДразнящего 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) 

Поскольку я не хочу, чтобы блок Тесты, которые будут обращаться к базе данных, как я могу издеваться поведение при условии, что:

  • Фиктивная библиотека не будет работать, так как 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 является линия

dbh = pyodbc.connect(db_credentials) 

Эта строка выполняется в то время как ваш импортируютview.py и вы не можете контролировать его без осуществления какой-то хак в тестовом коде, как латание подключить перед тем импорта view.py или что-нибудь еще импортирования Это.

Я хотел бы сильно отговорить вас от написания такого рода грязных трюков и немного изменить свой код для реализации ленивого dbh. Другой способ может написать собственную оболочку класса db (лучше) и исправить ее в ваших тестах, но это сильное изменение дизайна, и вы можете ввести его позже, воспользовавшись мощными реализованными тестами.

В 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_mock, db_mock и get_db_mock могут использоваться с разными поведением: мой опыт в том, что этот подход будет платить намного позже, пока вы добавим больше тестов.

+0

В этом случае должен быть db_mock другим объектом Mock? – tiagovrtr

+0

db_mock - еще один макет, и это то же самое, что исправленный возврат get_db в тестовом контексте –

+0

@tiagovrtr Я попытался сделать мой ответ более ясным. –