Модульные тесты для запроса в SQLAlchemy

Как идти о тестировании запросов в SQLAlchemy? Например, предположим, что у нас есть это models.py

 from sqlalchemy import ( Column, Integer, String, ) from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class Panel(Base): __tablename__ = 'Panels' id = Column(Integer, primary_key=True) category = Column(Integer, nullable=False) platform = Column(String, nullable=False) region = Column(String, nullable=False) def __init__(self, category, platform, region): self.category = category self.platform = platform self.region = region def __repr__(self): return ( "<Panel('{self.category}', '{self.platform}', " "'{self.region}')>".format(self=self) ) 

и этот tests.py

 import unittest from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from models import Base, Panel class TestQuery(unittest.TestCase): engine = create_engine('sqlite:///:memory:') Session = sessionmaker(bind=engine) session = Session() def setUp(self): Base.metadata.create_all(self.engine) self.session.add(Panel(1, 'ion torrent', 'start')) self.session.commit() def tearDown(self): Base.metadata.drop_all(self.engine) def test_query_panel(self): expected = [Panel(1, 'ion torrent', 'start')] result = self.session.query(Panel).all() self.assertEqual(result, expected) 

Когда мы пытаемся запустить тест, он терпит неудачу, хотя две панели выглядят одинаково.

 $ nosetests F ====================================================================== FAIL: test_query_panel (tests.TestQuery) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/clasher/tmp/tests.py", line 31, in test_query_panel self.assertEqual(result, expected) AssertionError: Lists differ: [<Panel('1', 'ion torrent', 's... != [<Panel('1', 'ion torrent', 's... First differing element 0: <Panel('1', 'ion torrent', 'start')> <Panel('1', 'ion torrent', 'start')> [<Panel('1', 'ion torrent', 'start')>, <Panel('2', 'ion torrent', 'end')>] ---------------------------------------------------------------------- Ran 1 test in 0.063s FAILED (failures=1) 

Одно из решений, которое я нашел, – сделать запрос для каждого отдельного экземпляра, который я ожидаю найти в запросе:

 class TestQuery(unittest.TestCase): ... def test_query_panel(self): expected = [ (1, 'ion torrent', 'start'), (2, 'ion torrent', 'end') ] successful = True # Check to make sure every expected item is in the query try: for category, platform, region in expected: self.session.query(Panel).filter_by( category=category, platform=platform, region=region).one() except (NoResultFound, MultipleResultsFound): successful = False self.assertTrue(successful) # Check to make sure no unexpected items are in the query self.assertEqual(self.session.query(Panel).count(), len(expected)) 

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

ваш оригинальный тест находится на правильном пути, вам просто нужно сделать одну из двух вещей: либо убедитесь, что два объекта Panel с одинаковым идентификатором первичного ключа сравниваются с True :

 class Panel(Base): # ... def __eq__(self, other): return isinstance(other, Panel) and other.id == self.id 

или вы можете организовать свой тест таким образом, чтобы убедиться, что вы проверяете тот же экземпляр Panel (потому что здесь мы используем карту идентификации ):

 class TestQuery(unittest.TestCase): def setUp(self): self.engine = create_engine('sqlite:///:memory:') self.session = Session(engine) Base.metadata.create_all(self.engine) self.panel = Panel(1, 'ion torrent', 'start') self.session.add(self.panel) self.session.commit() def tearDown(self): Base.metadata.drop_all(self.engine) def test_query_panel(self): expected = [self.panel] result = self.session.query(Panel).all() self.assertEqual(result, expected) 

что касается установки / отрыва двигателя / сеанса, я бы выбрал шаблон, в котором вы используете один движок для всех тестов, и если ваша схема фиксирована, единственная схема для всех тестов, то вы убедитесь, что данные, которые вы работаете с выполняется в транзакции, которая может быть откат. Session можно заставить работать таким образом, чтобы вызов commit() фактически не совершал «настоящую» транзакцию, завершая весь тест в явной Transaction . Этот пример используется в http://docs.sqlalchemy.org/en/rel_0_8/orm/session.html#joining-a-session-into-an-external-transaction . Наличие движка «: memory:» на каждом испытательном приборе займет много памяти и не будет масштабироваться в других базах данных, кроме SQLite.