Python SQLAlchemy - Насмешка над методом "desc" атрибута модели - PullRequest
7 голосов
/ 25 февраля 2011

В моем приложении есть класс для каждой модели, который содержит часто используемые запросы (я полагаю, это что-то вроде «Репозитория» на языке DDD).Каждому из этих классов передается объект сеанса SQLAlchemy для создания запросов при создании.У меня возникли небольшие затруднения с поиском лучшего способа утверждать, что в моих модульных тестах выполняются определенные запросы.Используя вездесущий пример блога, допустим, у меня есть модель «Post» с колонками и атрибутами «date» и «content».У меня также есть «PostRepository» с методом «find_latest», который должен запрашивать все сообщения в порядке убывания по «дате».Это выглядит примерно так:

from myapp.models import Post

class PostRepository(object):
    def __init__(self, session):
        self._s = session

    def find_latest(self):
        return self._s.query(Post).order_by(Post.date.desc())

У меня проблемы с насмешкой над вызовом Post.date.desc ().Прямо сейчас я обезьяна исправляю макет для Post.date.desc в моем модульном тесте, но я чувствую, что, вероятно, есть лучший подход.текущий модульный тест выглядит примерно так:

import unittest
import mox

class TestPostRepository(unittest.TestCase):

    def setUp(self):
        self._mox = mox.Mox()

    def _create_session_mock(self):
        from sqlalchemy.orm.session import Session
        return self._mox.CreateMock(Session)

    def _create_query_mock(self):
        from sqlalchemy.orm.query import Query
        return self._mox.CreateMock(Query)

    def _create_desc_mock(self):
        from myapp.models import Post
        return self._mox.CreateMock(Post.date.desc)

    def test_find_latest(self):
        from myapp.models.repositories import PostRepository
        from myapp.models import Post

        expected_result = 'test'

        session_mock = self._create_session_mock()
        query_mock = self._create_query_mock()
        desc_mock = self._create_desc_mock()

        # Monkey patch
        tmp = Post.date.desc
        Post.date.desc = desc_mock

        session_mock.query(Post).AndReturn(query_mock)
        query_mock.order_by(Post.date.desc().AndReturn('test')).AndReturn(query_mock)
        query_mock.offset(0).AndReturn(query_mock)
        query_mock.limit(10).AndReturn(expected_result)

        self._mox.ReplayAll()
        r = PostRepository(session_mock)

        result = r.find_latest()
        self._mox.VerifyAll()

        self.assertEquals(expected_result, result)

        Post.date.desc = tmp

Это работает, хотя и выглядит ужасно, и я не уверен, почему он не работает без части "AndReturn ('test')" из Post.date.desc() .AndReturn ( 'тест') "

Ответы [ 2 ]

13 голосов
/ 13 марта 2011

Я не думаю, что вы действительно получаете большую выгоду, используя насмешки для тестирования ваших запросов.Тестирование должно быть проверкой логики кода, а не реализации .Лучшим решением было бы создать новую базу данных, добавить в нее несколько объектов, выполнить запрос к этой базе данных и определить, возвращаете ли вы правильные результаты.Например:


# Create the engine. This starts a fresh database
engine = create_engine('sqlite://')
# Fills the database with the tables needed.
# If you use declarative, then the metadata for your tables can be found using Base.metadata
metadata.create_all(engine)
# Create a session to this database
session = sessionmaker(bind=engine)()

# Create some posts using the session and commit them
...

# Test your repository object...
repo = PostRepository(session)
results = repo.find_latest()

# Run your assertions of results
...

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

Следует иметь в виду, что у вас могут быть проблемы с поведением sqlite, отличным от другого типа базы данных,Использование sqlite в памяти дает вам быстрые тесты, но если вы хотите серьезно относиться к этим тестам, вы, вероятно, захотите запускать их для тех же типов баз данных, которые вы будете использовать и в рабочей среде.

1 голос
/ 12 июля 2018

Если вы все же хотите создать модульный тест с имитационным вводом, вы можете создать экземпляры вашей модели с поддельными данными

В случае, если прокси-сервер результата возвращает результат с более чем одной из моделей (например, при объединении двух таблиц), вы можете использовать collections структуру данных с именем namedtuple

Мы используем его для проверки результатов запросов на соединение

...