【问题标题】:Unittest.mock - how to mock a call to cursor.execute() from connection object?Unittest.mock - 如何模拟从连接对象对 cursor.execute() 的调用?
【发布时间】:2018-07-30 04:42:05
【问题描述】:

我正在尝试使用 mock 将以下代码中的 cursor.execute() 存根,以便我可以测试使用格式正确的查询调用执行:

// Module ABC

def buildAndExecuteQuery( tablesAndColumnsToSelect ):
   '''Build and execute a query.

   Args: tablesAndColumnsToSelect (dict)
         - keys are table names, values are columns

   '''
   query = ..... < logic to build query > ....

   from django.db import connections
   cursor = connections[ 'mydb' ].cursor()
   cursor.execute( query )

如何在python2.7中使用mock库完成这种类型的mock?

【问题讨论】:

  • mox 0.5.3: New uses of this library are discouraged.你有什么理由必须使用mox?
  • 并非如此,尽管这是我工作的常见做法。使用另一个模拟库的可行解决方案是可以的 - 如果不鼓励使用 mox 的新用途,我想会更好。
  • 我没有听说过mox,这只是 pypi 页面本身的引用。它自 2010 年以来也没有更新。我现在将尝试为您编写一个示例,但版本之间的导入略有不同(Python 3 模拟在 stdlib 中,但 Python 2 是第三方)。我猜是 Python 2,因为 mox 只是 Python 2?
  • 是 Python 2.7。没问题,感谢 Delirious Lettuce 的帮助!
  • 我已更新帖子以引用模拟库而不是 pymox。

标签: python mocking python-unittest magicmock


【解决方案1】:

我将用示例扩展@G_M 答案,因为我有两个反对意见:

  1. 在我看来,显式关闭数据库游标是一种很好的做法,更多信息请参见:Necessity of explicit cursor.close()。这可以通过将光标用作上下文管理器来完成,更多信息请参见 Django 文档:https://docs.djangoproject.com/en/dev/topics/db/sql/#connections-and-cursors
  2. 使用mock 时,我们不应在定义对象的位置修补对象:

基本原则是在查找对象的位置打补丁, 这不一定与定义它的地方相同。一种 几个例子将有助于澄清这一点。

参考:https://docs.python.org/3/library/unittest.mock.html#where-to-patch

示例:

我们要测试的函数:

# foooo_bar.py
from typing import Optional

from django.db import DEFAULT_DB_ALIAS, connections


def some_function(some_arg: str, db_alias: Optional[str] = DEFAULT_DB_ALIAS):
    with connections[db_alias].cursor() as cursor:
        cursor.execute('SOME SQL FROM %s;', [some_arg])

测试:

# test_foooo_bar.py
from unittest import mock

from django.db import DEFAULT_DB_ALIAS
from django.test import SimpleTestCase

from core.db_utils.xx import some_function


class ExampleSimpleTestCase(SimpleTestCase):
    @mock.patch('foooo_bar.connections')
    def test_some_function_executes_some_sql(self, mock_connections):
        mock_cursor = mock_connections.__getitem__(DEFAULT_DB_ALIAS).cursor.return_value.__enter__.return_value
        some_function('fooo')

        # Demonstrating assert_* options:
        mock_cursor.execute.assert_called_once()
        mock_cursor.execute.assert_called()
        mock_cursor.execute.assert_called_once_with('SOME SQL FROM %s;', ['fooo'])

【讨论】:

    【解决方案2】:

    由于我不知道您的查询逻辑是什么,我将query 修改为直接通过tables_and_columns_to_select 参数接受一个标记值。

    # b_and_ex_q.py
    
    
    def build_and_execute_query(tables_and_columns_to_select):
        """Build and execute a query.
    
        Args: tablesAndColumnsToSelect (dict)
              - keys are table names, values are columns
    
        """
    
        query = tables_and_columns_to_select  # ..... < logic to build query > ....
    
        from django.db import connections
    
        cursor = connections['mydb'].cursor()
        cursor.execute(query)
    

    import unittest
    
    from mock import patch, sentinel
    
    from b_and_ex_q import build_and_execute_query
    
    
    class TestCursorExecute(unittest.TestCase):
        @patch('django.db.connections')
        def test_build_and_execute_query(self, mock_connections):
            # Arrange
            mock_cursor = mock_connections.__getitem__('mydb').cursor.return_value
    
            # Act
            build_and_execute_query(sentinel.t_and_c)
    
            # Assert
            mock_cursor.execute.assert_called_once_with(sentinel.t_and_c)
    
    
    if __name__ == '__main__':
        unittest.main()
    

    【讨论】:

    • 感谢 Delirious Lettuce,这让我一整天都感到困惑。顺便说一句,我发现在 buildAndExecuteQuery() 中申请本地“从 django.db 导入连接”导入的补丁很简单:@patch('django.db.connections')。
    • @bwrabbit 没问题,我还根据您的评论更新了补丁。很高兴在这里见到一位温哥华人!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-13
    • 2020-01-08
    • 1970-01-01
    相关资源
    最近更新 更多