Python SQLAlchemy - 模拟模型属性的“desc”方法
在我的应用程序中,每个模型都有一个类来保存常用的查询(我猜它有点像 DDD 语言中的“存储库”)。这些类中的每一个都会传递 SQLAlchemy 会话对象以在构造时创建查询。我在找出断言某些查询正在单元测试中运行的最佳方法时遇到了一些困难。使用无处不在的博客示例,假设我有一个“Post”模型,其中包含列和属性“日期”和“内容”。我还有一个“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
这确实有效,尽管感觉很丑,而且我不确定为什么它会在没有“Post.AndReturn('test')”部分的情况下失败。 date.desc().AndReturn('测试')"
In my application, there is a class for each model that holds commonly used queries (I guess it's somewhat of a "Repository" in DDD language). Each of these classes is passed the SQLAlchemy session object to create queries with upon construction. I'm having a little difficulty in figuring the best way to assert certain queries are being run in my unit tests. Using the ubiquitous blog example, let's say I have a "Post" model with columns and attributes "date" and "content". I also have a "PostRepository" with the method "find_latest" that is supposed to query for all posts in descending order by "date". It looks something like:
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())
I'm having trouble mocking the Post.date.desc() call. Right now I'm monkey patching a mock in for Post.date.desc in my unit test, but I feel that there is likely a better approach.
Edit: I'm using mox for mock objects, my current unit test looks something like:
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
This does work, though feels ugly and I'm not sure why it fails without the "AndReturn('test')" piece of "Post.date.desc().AndReturn('test')"
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我认为使用模拟来测试查询并没有真正获得太多好处。测试应该测试代码的逻辑,而不是实现。更好的解决方案是创建一个新的数据库,向其中添加一些对象,在该数据库上运行查询,并确定是否返回正确的结果。例如:
现在,您实际上正在测试代码的逻辑。这意味着您可以更改方法的实现,但只要查询正常工作,测试仍然应该通过。如果需要,您可以将此方法编写为获取所有对象的查询,然后对结果列表进行切片。测试将会通过,正如它应该的那样。稍后,您可以更改实现以使用 SA 表达式 API 运行查询,测试就会通过。
需要记住的一件事是,您可能会遇到 sqlite 的行为与其他数据库类型不同的问题。使用内存中的 sqlite 可以为您提供快速测试,但如果您想认真对待这些测试,您可能需要针对您将在生产中使用的相同类型的数据库运行它们。
I don't think you're really gaining much benefit by using mocks for testing your queries. Testing should be testing the logic of the code, not the implementation. A better solution would be to create a fresh database, add some objects to it, run the query on that database, and determine if you're getting the correct results back. For example:
Now, you're actually testing the logic of the code. This means that you can change the implementation of your method, but so long as the query works correctly, the tests should still pass. If you want, you could write this method as a query that gets all the objects, then slices the resulting list. The test would pass, as it should. Later on, you could change the implementation to run the query using the SA expression APIs, and the test would pass.
One thing to keep in mind is that you might have problems with sqlite behaving differently than another database type. Using sqlite in-memory gives you fast tests, but if you want to be serious about these tests, you'll probably want to run them against the same type of database you'll be using in production as well.
如果您还想使用模拟输入创建单元测试,则可以使用虚假数据创建模型实例,
以防结果代理返回包含来自多个模型的数据的结果(例如,当您连接两个表时),您可以使用名为
namedtuple
的collections
数据结构,我们使用它来模拟连接查询的结果
If yet you want to create a unit test with mock input, you can create instances of your model with fake data
In case that the result proxy return result with data from more than one of the models (for instance when you join two tables), you can use
collections
data struct callednamedtuple
We are using it to mock results of join queries