Python SQLAlchemy-模拟模型属性的"desc"方法 [英] Python SQLAlchemy - Mocking a model attribute's "desc" method

查看:317
本文介绍了Python SQLAlchemy-模拟模型属性的"desc"方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的应用程序中,每个模型都有一个用于保存常用查询的类(我想它有点像DDD语言中的存储库").这些类中的每一个都传递给SQLAlchemy会话对象,以在构造时创建查询.我很难确定断言某些查询在单元测试中正在运行的最佳方式.使用无处不在的博客示例,假设我有一个具有列和属性日期"和内容"的发布"模型.我还有一个方法为"find_latest"的"PostRepository",应该按日期"降序查询所有帖子.看起来像这样:

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())

我在模拟Post.date.desc()调用时遇到麻烦.现在,我正在单元测试中为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.

我正在将mox用于模拟对象,我当前的单元测试如下:

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

这确实有效,尽管感觉很丑,而且我不确定为什么没有"Post.date.desc().AndReturn('test')"的"AndReturn('test')"部分会失败

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')"

推荐答案

我认为使用模拟测试您的查询不会真正让您受益匪浅.测试应该测试代码的逻辑,而不是实现.更好的解决方案是创建一个新的数据库,向其中添加一些对象,在该数据库上运行查询,然后确定是否返回正确的结果.例如:

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:


# 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运行查询,然后测试就会通过.

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.

要记住的一件事是,sqlite的行为可能与另一种数据库类型有所不同.使用sqlite内存可以为您提供快速测试,但是如果您想认真对待这些测试,则可能希望对生产环境中使用的同一类型的数据库运行它们.

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.

这篇关于Python SQLAlchemy-模拟模型属性的"desc"方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆