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

标签 python unit-testing mocking sqlalchemy mox

在我的应用程序中,每个模型都有一个类来保存常用查询(我猜它有点像 DDD 语言中的“存储库”)。这些类中的每一个都被传递给 SQLAlchemy session 对象,以在构造时创建查询。我在确定断言某些查询正在我的单元测试中运行的最佳方法时遇到了一些困难。使用无处不在的博客示例,假设我有一个包含列和属性“日期”和“内容”的“帖子”模型。我还有一个带有方法“find_latest”的“PostRepository”,它应该按“日期”降序查询所有帖子。它看起来像:

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.date.desc().AndReturn('test')”的“AndReturn('test')”片段会失败

最佳答案

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


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

关于Python SQLAlchemy - 模拟模型属性的 "desc"方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5121595/

相关文章:

Python Tkinter 刷新文本小部件中的文本

python - 导入错误 : No Module Named <parent dir>

java - Oauth 进程中从 JIRA 获取请求 token 时出错

mocking - Nightwatch 模拟 HTTP 请求

java - 单元测试 - 检查方法调用是否具有不覆盖 equals 的对象

python - 无法从 Docker 容器内部连接到 Redis

java - 使用 Freemarker 对 Spring Boot 应用程序进行单元测试抛出 NoSuchMessageException

c# - 无法让模拟方法返回 null

c - 开发中如何编写单元测试?

java - 使用模拟构造函数参数模拟抽象类?