Python SQLAlchemy - 模拟模型属性的“desc”方法

发布于 2024-10-19 19:04:39 字数 2093 浏览 1 评论 0原文

在我的应用程序中,每个模型都有一个类来保存常用的查询(我猜它有点像 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

贩梦商人 2024-10-26 19:04:39

我认为使用模拟来测试查询并没有真正获得太多好处。测试应该测试代码的逻辑,而不是实现。更好的解决方案是创建一个新的数据库,向其中添加一些对象,在该数据库上运行查询,并确定是否返回正确的结果。例如:


# 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 可以为您提供快速测试,但如果您想认真对待这些测试,您可能需要针对您将在生产中使用的相同类型的数据库运行它们。

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
...

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.

ま柒月 2024-10-26 19:04:39

如果您还想使用模拟输入创建单元测试,则可以使用虚假数据创建模型实例,

以防结果代理返回包含来自多个模型的数据的结果(例如,当您连接两个表时),您可以使用名为 namedtuplecollections 数据结构,

我们使用它来模拟连接查询的结果

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 called namedtuple

We are using it to mock results of join queries

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文