2009-08-25 4 views
11

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

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

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

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

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

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

ответ

5

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

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

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

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

2

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

sqlite_memory_db = create_engine('sqlite://') 

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

+0

Привет, потому что некоторые из моих коллег настаивают на том, что мы должны использовать процедуру хранения, и я не имею никакого контроля над этим, поэтому sqlite не является возможным выбором для меня. – ablmf

+0

BTW: sqlite не поддерживает процедуру хранения. – ablmf

+0

Hm. Это означает, что вам придется издеваться над значительными частями кода (хранимые процедуры). Это сделает тесты намного менее полезными. Трудная ситуация. –

0

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

+0

Спасибо @brool, но эта ссылка сейчас не работает. :( –

2

Один из способов захвата изменений в базу данных, чтобы использовать механизм расширения сеанса 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().

Смежные вопросы