正如我在评论中提到的你之前的问题 https://stackoverflow.com/questions/76530308, 我认为unit测试不应该处理以下数据库any种,而是模拟所有与数据库交互的函数。
建议的方法
这是我如何为 CRUD 函数编写单元测试的示例create_new_item
.
首先是用于演示目的的简化设置:
from sqlmodel import Field, SQLModel, Session
class ItemCreate(SQLModel):
x: str
y: float
class Item(ItemCreate, table=True):
id: int | None = Field(primary_key=True)
def create_new_item(db_session: Session, *, obj_input: ItemCreate) -> Item:
db_obj = Item.from_orm(obj_input)
db_session.add(db_obj)
db_session.commit()
db_session.refresh(db_obj)
return db_obj
测试用例:
from unittest import TestCase
from unittest.mock import call, create_autospec
from sqlmodel import Session
# ... import Item, ItemCreate
class MyTestCase(TestCase):
def test_create_new_item(self) -> None:
test_item = ItemCreate(x="foo", y=3.14)
mock_session = create_autospec(Session, instance=True)
expected_output = Item.from_orm(test_item)
expected_session_calls = [
call.add(expected_output),
call.commit(),
call.refresh(expected_output),
]
output = create_new_item(mock_session, obj_input=test_item)
self.assertEqual(expected_output, output)
self.assertListEqual(expected_session_calls, mock_session.mock_calls)
请注意,测试从未连接到任何数据库。我们must假设 SQLAlchemy 函数(以及 SQLModel 的扩展函数)如宣传的那样工作。否则我们一开始就不应该使用它们。我们的单元测试需要确保我们正确调用这些函数。
更大程度的隔离
从技术上来说,我们现在测试这个功能的方式,我们仍然依赖我们编写的其他代码以某种方式发挥作用,即模型。在我看来,这是一个灰色地带,因为从技术上讲,我们没有为这些类编写任何方法,而是依赖于基类的现有方法。
But you could认为模型定义本身应该与create_new_item
功能。换句话说,单元测试的成功或失败不应该取决于我们的模型定义是否Item
and ItemCreate
match以一种允许一个从另一个构建的方式。
因此,具有最严格隔离的测试可能如下所示:
from unittest import TestCase
from unittest.mock import MagicMock, call, create_autospec, patch
from sqlmodel import Session
# ... import Item
class MyTestCase(TestCase):
@patch.object(Item, "from_orm")
def test_create_new_item2(self, mock_from_orm: MagicMock) -> None:
test_item = MagicMock()
mock_session = create_autospec(Session, instance=True)
expected_output = mock_from_orm.return_value = object()
expected_session_calls = [
call.add(expected_output),
call.commit(),
call.refresh(expected_output),
]
output = create_new_item(mock_session, obj_input=test_item)
self.assertEqual(expected_output, output)
self.assertListEqual(expected_session_calls, mock_session.mock_calls)
减少孤立:迈向一体化的一步
如果您想要进行单独类型的测试does执行数据库查询以检查结果是否符合预期,您可以注入绑定到 SQLite 内存数据库引擎的不同会话对象。
from unittest import TestCase
from sqlmodel import SQLModel, Session, create_engine, select
# ... import Item, ItemCreate
class MyTestCase(TestCase):
def test_create_new_item3(self) -> None:
engine = create_engine("sqlite:///")
SQLModel.metadata.create_all(engine)
test_item = ItemCreate(x="foo", y=3.14)
expected_output = Item(**test_item.dict(), id=1)
with Session(engine) as session:
output = create_new_item(session, obj_input=test_item)
self.assertEqual(expected_output, output)
result = session.exec(select(Item)).all()
self.assertListEqual([expected_output], result)
当然,如果您的模型具有与 SQLite 不兼容的特定于数据库的设置(例如 Postgres 架构),则这将不起作用。
在这种情况下,您有多种选择,如上一个问题中所述。例如,您实际上可以创建一个单独的测试数据库,并围绕测试编写一些隔离/清理逻辑。然后提供一个连接到该测试数据库的引擎。
或者您可以尝试巧妙地围绕这些特定设置进行猴子修补。