在我的应用程序中,每个模型都有一个类来保存常用的查询(我猜它有点像DDD语言中的“存储库”)。这些类中的每一个都传递给SQLAlchemy会话对象,以便在构造时创建查询。我在确定在单元测试中运行某些查询的最佳方法时遇到了一些困难。使用无处不在的博客示例,假设我有一个“Post”模型,其中包含列和属性“date”和“content”。我还有一个“PostRepository”,其方法是“find_latest”,它应该按“日期”降序查询所有帖子。它看起来像:
from myapp.models import Post
class PostRepository(object):
def __init__(self, session):
self._s = session
def find_latest(self):
return self._s.query(Post).order_by(Post.date.desc())
我在模拟Post.date.desc()调用时遇到问题。现在我在我的单元测试中为Post.date.desc修补了一个模拟器,但我觉得可能有更好的方法。
编辑:我正在使用mox作为模拟对象,我当前的单元测试看起来像:
import unittest
import mox
class TestPostRepository(unittest.TestCase):
def setUp(self):
self._mox = mox.Mox()
def _create_session_mock(self):
from sqlalchemy.orm.session import Session
return self._mox.CreateMock(Session)
def _create_query_mock(self):
from sqlalchemy.orm.query import Query
return self._mox.CreateMock(Query)
def _create_desc_mock(self):
from myapp.models import Post
return self._mox.CreateMock(Post.date.desc)
def test_find_latest(self):
from myapp.models.repositories import PostRepository
from myapp.models import Post
expected_result = 'test'
session_mock = self._create_session_mock()
query_mock = self._create_query_mock()
desc_mock = self._create_desc_mock()
# Monkey patch
tmp = Post.date.desc
Post.date.desc = desc_mock
session_mock.query(Post).AndReturn(query_mock)
query_mock.order_by(Post.date.desc().AndReturn('test')).AndReturn(query_mock)
query_mock.offset(0).AndReturn(query_mock)
query_mock.limit(10).AndReturn(expected_result)
self._mox.ReplayAll()
r = PostRepository(session_mock)
result = r.find_latest()
self._mox.VerifyAll()
self.assertEquals(expected_result, result)
Post.date.desc = tmp
这确实有效,虽然感觉很难看,但我不知道为什么没有“AndReturn('test')”片段“Post.date.desc()。”AndReturn('test')“
答案 0 :(得分:13)
我认为使用模拟测试您的查询并不会带来太多好处。测试应该测试代码的逻辑,而不是实现。一个更好的解决方案是创建一个新的数据库,向其添加一些对象,在该数据库上运行查询,并确定您是否得到了正确的结果。例如:
# Create the engine. This starts a fresh database
engine = create_engine('sqlite://')
# Fills the database with the tables needed.
# If you use declarative, then the metadata for your tables can be found using Base.metadata
metadata.create_all(engine)
# Create a session to this database
session = sessionmaker(bind=engine)()
# Create some posts using the session and commit them
...
# Test your repository object...
repo = PostRepository(session)
results = repo.find_latest()
# Run your assertions of results
...
现在,您实际上正在测试代码的逻辑。这意味着您可以更改方法的实现,但只要查询正常工作,测试仍应通过。如果需要,可以将此方法编写为获取所有对象的查询,然后对结果列表进行切片。测试将通过,因为它应该。稍后,您可以使用SA表达式API更改实现以运行查询,并且测试将通过。
要记住的一件事是,您可能遇到sqlite行为与另一种数据库类型不同的问题。在内存中使用sqlite可以为您提供快速测试,但如果您想认真对待这些测试,您可能希望针对您将在生产中使用的相同类型的数据库运行它们。
答案 1 :(得分:0)
如果您还想使用模拟输入创建单元测试,则可以使用假数据创建模型的实例
如果结果代理返回来自多个模型的数据的结果(例如,当您连接两个表时),则可以使用名为collections
的{{1}}数据结构
我们正在使用它来模拟联接查询的结果