Насмешливый модуль pyodbc вызывает модульные тесты django

Я хочу сделать модульные тесты для некоторых представлений django, которые используют пользовательское соединение с базой данных pyodbc.

просмотры.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)

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

  • Библиотека mock не будет работать, так как pyodbc является расширением Python C.
  • Использование sys.modules не работает, вероятно, потому, что модуль используется в views.py, а не в test.py.

Вот мой тест-драйвер

тесты.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)

person tiagovrtr    schedule 29.04.2016    source источник


Ответы (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 документацию по фреймворку. Раньше я исправлял соединение в методе setUp(), потому что могу предположить, что он нужен вам почти во всех ваших тестах в этом классе, где cursor_mock, db_mock и get_db_mock могут использоваться с различным поведением: мой опыт показывает, что этот подход окупится намного позже, пока вы добавлю больше тестов.

person Michele d'Amico    schedule 05.05.2016
comment
В этом случае должен ли db_mock быть другим объектом Mock? - person tiagovrtr; 05.05.2016
comment
db_mock — это еще один макет, и это то же самое, что исправленный get_db возвращает в тестовом контексте. - person Michele d'Amico; 05.05.2016