Как организовать слой доступа к базе данных? - PullRequest
11 голосов
/ 25 августа 2009

Я использую SqlAlchemy, библиотеку ORM на python. И я использовал для доступа к базе данных напрямую с бизнес-уровня напрямую, вызывая SqlAlchemy API.

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

Я думаю, что есть 2 варианта:

  1. использовать один класс, который содержит соединение с БД и множество методов, таких как addUser / delUser / updateUser, addBook / delBook / updateBook. Но это означает, что этот класс будет очень большим.

  2. Другой подход заключается в создании различных классов менеджера, таких как «UserManager», «BookManager». Но это означает, что я должен передать список менеджеров бизнес-уровню, который кажется немного громоздким.

Как вы организуете слой базы данных?

Ответы [ 4 ]

5 голосов
/ 25 августа 2009

Хороший вопрос!
Проблема не является тривиальной и может потребовать нескольких подходов для ее решения. Например:

  1. Организуйте код, чтобы вы могли тестировать большую часть логики приложения, не обращаясь к базе данных. Это означает, что у каждого класса будут методы доступа к данным и методы их обработки, а вторые могут быть легко протестированы.
  2. Когда вам нужно проверить доступ к базе данных, вы можете использовать прокси (например, решение № 1); Вы можете думать об этом как о двигателе для SqlAlchemy или как о замене SA. В обоих случаях вы можете подумать о самоинициализирующейся подделке .
  3. Если код не включает хранимые процедуры, подумайте об использовании баз данных в памяти, как говорит Леннарт (даже если в этом случае называть его «модульным тестом» может показаться немного странным!).

Однако, исходя из моего опыта, все довольно легко на словах, а затем резко падает, когда вы выходите на поле. Например, что делать, когда большая часть логики содержится в инструкциях SQL? Что если доступ к данным строго чередуется с их обработкой? Иногда вы можете выполнить рефакторинг, иногда (особенно в больших и устаревших приложениях) нет.

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

2 голосов
/ 25 августа 2009

Один из способов перехватить изменения в базе данных - это использовать механизм расширения сеанса SQLAlchemy и перехватывать сброс к базе данных, используя что-то вроде этого:

from sqlalchemy.orm.attributes import instance_state
from sqlalchemy.orm import SessionExtension

class MockExtension(SessionExtension):
    def __init__(self):
        self.clear()

    def clear(self):
        self.updates = set()
        self.inserts = set()
        self.deletes = set()

    def before_flush(self, session, flush_context, instances):
        for obj in session.dirty:
            self.updates.add(obj)
            state = instance_state(obj)
            state.commit_all({})
            session.identity_map._mutable_attrs.discard(state)
            session.identity_map._modified.discard(state)

        for obj in session.deleted:
            self.deletes.add(obj)
            session.expunge(obj)

        self.inserts.update(session.new)
        session._new = {}

Затем для тестов вы можете настроить сеанс с этим макетом и посмотреть, соответствует ли он вашим ожиданиям.

mock = MockExtension()
Session = sessionmaker(extension=[mock], expire_on_commit=False)

def do_something(attr):
    session = Session()
    obj = session.query(Cls).first()
    obj.attr = attr
    session.commit()

def test_something():
    mock.clear()
    do_something('foobar')
    assert len(mock.updates) == 1
    updated_obj = mock.updates.pop()
    assert updated_obj.attr == 'foobar'

Но вы все равно захотите провести хотя бы несколько тестов с базой данных, потому что по крайней мере захотите узнать, работают ли ваши запросы так, как ожидалось. И имейте в виду, что вы также можете вносить изменения в базу данных через session.update(), .delete() и .execute().

2 голосов
/ 25 августа 2009

Я бы установил соединение с базой данных во время тестирования, которое вместо этого соединяется с базой данных в памяти. Вот так:

sqlite_memory_db = create_engine('sqlite://')

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

0 голосов
/ 25 августа 2009

SQLAlchemy имеет некоторые возможности для облегчения насмешек - может быть, это будет проще, чем пытаться переписать целые разделы вашего проекта?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...