使用SqlAlchemy Automap生成模型的查询函数单元测试报错及测试实现疑问
我来帮你一步步梳理和解决这些测试问题~
一、解决ArgumentError: Column expression...报错
问题根源
你在测试中用mocker.patch('some_path.main.Model1')把整个Model1替换成了MagicMock对象,导致Model1.CustID变成了一个Mock实例。而SqlAlchemy的select()方法需要接收真实的Column表达式,Mock对象根本不满足SqlAlchemy的语法校验,所以抛出了这个错误。
而且你使用的UnifiedAlchemyMagicMock本身就是专门用来模拟SqlAlchemy Session的工具,完全不需要额外Patch模型类,这属于画蛇添足的操作。
修正后的测试代码
去掉不必要的Model1 Patch,直接用UnifiedAlchemyMagicMock的模拟能力即可:
from mock_alchemy.mocking import UnifiedAlchemyMagicMock from main import get_info import pytest @pytest.fixture def session(): return UnifiedAlchemyMagicMock() def test_info(session): # 直接配置Session mock的返回值 session.execute.return_value.scalar.return_value = 1 result = get_info(session, 1) assert result == 1
为什么返回None的测试能通过?
你提到把返回值换成None测试就通过了,是因为那个测试里你去掉了mocker.patch('some_path.main.Model1'),此时Model1是真实的类,Model1.CustID是合法的Column对象,SqlAlchemy能正常解析查询语法,再加上Session的mock返回None,断言自然能通过。
二、测试复杂查询时,是否需要创建本地模型?
你在test_new_main.py里纠结是否要创建本地Model1,其实分两种场景来看:
场景1:仅做单元测试(验证函数逻辑)
如果你只是想验证函数的返回值是否符合预期,继续用UnifiedAlchemyMagicMock模拟Session就行,完全不需要创建本地模型或真实数据库,直接配置mock返回值即可:
def test_get_expense_type_id(session): # 配置mock返回预期的ExpID session.execute.return_value.scalar.return_value = 2 result = get_expense_type_id(session, 1) assert result == 2 # 可选:验证查询是否符合预期(比如检查filter条件) session.execute.assert_called_once() called_select = session.execute.call_args[0][0] assert Model1.ExpID in called_select.columns
场景2:做集成测试(验证查询语法逻辑)
如果你想验证查询的实际逻辑是否正确(比如notin_过滤是否生效、多条件组合是否正确),那创建本地模型+内存数据库的方式非常合适,这能让测试更贴近真实生产场景。
注意修正测试代码里的小问题:你写的ExpenseReportItemType应该是你定义的Model1,修正后的代码如下:
from sqlalchemy import create_engine, Column, Integer, String from sqlalchemy.orm import sessionmaker, DeclarativeBase from new_main import get_expense_type_id import pytest class Base(DeclarativeBase): pass class Model1(Base): __tablename__ = 'tbl_dummy' ExpID = Column(Integer, primary_key=True) CustID = Column(Integer) Type = Column(String(30)) Name = Column(String(30)) @pytest.fixture(scope='session') def test_session(): engine = create_engine('sqlite+pysqlite:///:memory:', echo=True, future=True) Base.metadata.create_all(engine) Session = sessionmaker(bind=engine) return Session() def test_get_expense_type_id(test_session): with test_session as s: # 插入测试数据:一条符合条件,一条会被notin过滤 s.add_all([ Model1(CustID=1, Type="LineItem", Name="Name1", ExpID=2), Model1(CustID=1, Type="LineItem", Name="a", ExpID=3) ]) s.commit() # 调用待测试函数 result = get_expense_type_id(s, 1) # 断言结果:只有Name不在排除列表的那条数据会被返回 assert result == 2
总结
- 单元测试:用
UnifiedAlchemyMagicMock模拟Session,快速验证函数返回逻辑,无需真实数据库。 - 集成测试:用内存数据库+本地模型,验证查询语法和过滤逻辑的正确性,测试更严谨。
备注:内容来源于stack exchange,提问作者Manika Midha

