Насмешливый объект QuerySet в Django для того, чтобы протестировать функцию, которая принимает объект QuerySet


У меня есть служебная функция в моем проекте Django, она берет queryset, получает из него некоторые данные и возвращает результат. Я хотел бы написать несколько тестов для этой функции. Есть ли вообще "издеваться" над QuerySet? Я хотел бы создать объект, который не касается базы данных, и я могу предоставить ему список значений для использования (т. е. некоторые поддельные строки), а затем он будет действовать так же, как queryset, и позволит кому-то делать поиск полей на нем/filter/get/all и т. д.

Существует ли что-нибудь подобное уже?

7 25

7 ответов:

Насколько мне известно, нет, но почему бы не использовать настоящий queryset? Платформа тестирования настроена так, чтобы вы могли создавать образцы данных в тесте, и база данных создается заново на каждом тесте, так что нет никаких причин не использовать реальную вещь.

Конечно, вы можете издеваться над QuerySet, вы можете издеваться над чем угодно.

Вы можете создать объект самостоятельно, дать ему необходимый интерфейс и заставить его возвращать любые данные, которые вам нравятся. В глубине души насмешка - это не что иное, как предоставление "тестового двойника", который действует достаточно похоже на реальную вещь для целей ваших тестов.

Самый простой способ начать работу-это определить объект:

class MockQuerySet(object):
    pass
Затем создайте один из них и передайте его вашему тесту. Тест провалится, скорее всего, на AttributeError. Это подскажет вам, что вам нужно реализовать на вашем MockQuerySet. Повторяйте, пока ваш объект не станет достаточно богатым для ваших тестов.

У меня та же проблема, и похоже, что какой-то хороший человек написал библиотеку для насмешливых запросов, она называется mock-django и конкретный код, который вам понадобится, находится здесь https://github.com/dcramer/mock-django/blob/master/mock_django/query.py я думаю, что вы можете просто исправить функцию models objects, чтобы вернуть один из этих объектов QuerySetMock, которые вы настроили, чтобы вернуть что-то ожидаемое!

Для пустого Queryset я бы просто использовал none, как кейтхакбарт уже заявил.

Однако, чтобы имитировать набор запросов, который возвращает список значений, я предпочитаю использоватьимитацию С spec менеджера модели. В качестве примера (Python 2.7 style - я использовал внешнюю библиотеку макетов), Вот простой тест, где Queryset фильтруется, а затем подсчитывается:

from django.test import TestCase
from mock import Mock

from .models import Example


def queryset_func(queryset, filter_value):
    """
    An example function to be tested
    """
    return queryset.filter(stuff=filter_value).count()


class TestQuerysetFunc(TestCase):

    def test_happy(self):
        """
        `queryset_func` filters provided queryset and counts result
        """
        m_queryset = Mock(spec=Example.objects)
        m_queryset.filter.return_value = m_queryset
        m_queryset.count.return_value = 97

        result = func_to_test(m_queryset, '__TEST_VALUE__')

        self.assertEqual(result, 97)
        m_queryset.filter.assert_called_once_with(stuff='__TEST_VALUE__')
        m_queryset.count.assert_called_once_with()

Однако, чтобы выполнить вопрос, вместо того, чтобы устанавливать return_value для count, это может быть легко скорректировано, чтобы быть list экземпляров модели, возвращенных из all.

Обратите внимание, что цепочка обрабатывается установкой filter для возврата издевательского queryset:

m_queryset.filter.return_value = m_queryset

Это должно быть применено к любым методам queryset, используемым в тестируемой функции, например exclude и т. д.

Для этого я использую Джанго .функция none () .

Например:

class Location(models.Model):
  name = models.CharField(max_length=100)
mock_locations = Location.objects.none()

Этот метод часто используется в собственных внутренних тестовых случаях Django. На основе комментариев в коде

Calling none() will create a queryset that never returns any objects and no
+query will be executed when accessing the results. A qs.none() queryset
+is an instance of ``EmptyQuerySet``.

Попробуйте django_mock_queries библиотека , которая позволяет имитировать доступ к базе данных и все еще использовать некоторые функции набора запросов Django, такие как фильтрация.

Полное раскрытие: я внес некоторые особенности в проект.

Одним из первых Советов было бы разделить функцию на две части, одна из которых создает queryset и тот, который манипулирует его выходом. Таким образом, тестирование второй части является простым.

Для проблемы базы данных я исследовал, использует ли django sqlite-in-memory, и обнаружил, что последняя версия django использует базу данных sqlite-in-memory, начиная с страницы Django unittest:

При использовании компонента SQLite database engine тесты по умолчанию будут использовать база данных в памяти (т. е. база данных будет создана в памяти, полностью минуя файловую систему!).

Насмешки над объектом QuerySet не заставят вас использовать его полную логику.