【问题标题】:Unit tests for Query in SQLAlchemySQLAlchemy 中查询的单元测试
【发布时间】:2013-01-21 02:04:54
【问题描述】:

如何在 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))

不过,这让我觉得非常难看,而且我什至没有达到我要测试的复杂过滤查询的地步。有没有更优雅的解决方案,还是我总是必须手动进行一堆单独的查询?

【问题讨论】:

    标签: python unit-testing sqlalchemy


    【解决方案1】:

    您的原始测试在正确的轨道上,您只需做以下两件事之一:要么确保具有相同主键标识的两个 Panel 对象与 True 进行比较:

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

    或者您可以组织您的测试,以确保您检查的是同一个 Panel 实例(因为这里我们利用了 identity map):

    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 中。 https://docs.sqlalchemy.org/en/latest/orm/session_transaction.html#joining-a-session-into-an-external-transaction-such-as-for-test-suites 的示例说明了这种用法。在每个测试夹具上都有一个“:memory:”引擎会占用大量内存,并且不会真正扩展到除 SQLite 之外的其他数据库。

    【讨论】:

    • 这里的关键思想是,您需要在设置期间实例化所有对象,通过将它们作为属性分配给self 来保持它们,并稍后检索它们,而不是通过再次查询数据库,而是通过那些self 属性。此外,实现__eq__ 是不必要的;似乎 SQLAlchemy 将返回完全相同的模型实例(即,created_model_instance is instance_from_query 返回True)。最后,修改答案以使用事务回滚模式会有所帮助,即使它可以通过阅读提供的链接中的 SQLAlchemy 文档来推断。
    • 随着 SQLAlchemy 2.0 的发布,@zzzeek 的答案在 2022 年是否完全不同?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-25
    • 2013-11-26
    • 2016-06-17
    • 1970-01-01
    相关资源
    最近更新 更多