Unittest.mock - 如何模拟连接对象对 cursor.execute() 的调用?
Unittest.mock - how to mock a call to cursor.execute() from connection object?
我试图在下面的代码中用 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 中完成此类模拟?
因为我不知道你的查询逻辑是什么,所以我修改了 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()
我将通过示例扩展@G_M 的回答,因为我有两个反对意见:
- 在我看来,显式关闭数据库游标是一种很好的做法,更多信息请参见:Necessity of explicit cursor.close(). This can be done by using the cursor as a context manager, more in Django docs: https://docs.djangoproject.com/en/dev/topics/db/sql/#connections-and-cursors。
- 使用
mock
时,我们不应在定义对象的地方修补对象:
The basic principle is that you patch where an object is looked up,
which is not necessarily the same place as where it is defined. A
couple of examples will help to clarify this.
参考: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'])
我试图在下面的代码中用 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 中完成此类模拟?
因为我不知道你的查询逻辑是什么,所以我修改了 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()
我将通过示例扩展@G_M 的回答,因为我有两个反对意见:
- 在我看来,显式关闭数据库游标是一种很好的做法,更多信息请参见:Necessity of explicit cursor.close(). This can be done by using the cursor as a context manager, more in Django docs: https://docs.djangoproject.com/en/dev/topics/db/sql/#connections-and-cursors。
- 使用
mock
时,我们不应在定义对象的地方修补对象:
The basic principle is that you patch where an object is looked up, which is not necessarily the same place as where it is defined. A couple of examples will help to clarify this.
参考: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'])