Python 使用 pytest_mock 在函数中模拟多个查询

标签 python python-3.x unit-testing pytest psycopg2

我正在为一个包含多个sql查询的函数编写单元测试用例。我正在使用psycopg2模块并尝试模拟光标

app.py

import psycopg2

def my_function():
    # all connection related code goes here ...

    query = "SELECT name,phone FROM customer WHERE name='shanky'"
    cursor.execute(query)
    columns = [i[0] for i in cursor.description]
    customer_response = []
    for row in cursor.fetchall():
        customer_response.append(dict(zip(columns, row)))

    query = "SELECT name,id FROM product WHERE name='soap'"
    cursor.execute(query)
    columns = [i[0] for i in cursor.description]
    product_response = []
    for row in cursor.fetchall():
        product_response.append(dict(zip(columns, row)))

    return product_response

测试.py

from pytest_mock import mocker
import psycopg2

def test_my_function(mocker):
    from my_module import app
    mocker.patch('psycopg2.connect')

    #first query
    mocked_cursor_one = psycopg2.connect.return_value.cursor.return_value
    mocked_cursor_one.description = [['name'],['phone']]
    mocked_cursor_one.fetchall.return_value = [('shanky', '347539593')]
    mocked_cursor_one.execute.call_args == "SELECT name,phone FROM customer WHERE name='shanky'"

    #second query
    mocked_cursor_two = psycopg2.connect.return_value.cursor.return_value
    mocked_cursor_two.description = [['name'],['id']]
    mocked_cursor_two.fetchall.return_value = [('nirma', 12313)]
    mocked_cursor_two.execute.call_args == "SELECT name,id FROM product WHERE name='soap'"

    ret = app.my_function()
    assert ret == {'name' : 'nirma', 'id' : 12313}

但是模拟者总是采用最后一个模拟对象(第二个查询)。我已经尝试了多次黑客攻击,但没有成功。如何在一个函数中模拟多个查询并成功通过单元测试用例?是否可以以这种方式编写单元测试用例,或者我是否需要将查询拆分为不同的函数?

最佳答案

在深入研究文档之后,我能够在 @Pavel Vergeev 建议的 unittest 模拟装饰器和 side_effect 的帮助下实现这一目标。能够编写足以测试功能的单元测试用例。

from unittest import mock
from my_module import app

@mock.patch('psycopg2.connect')
def test_my_function(mocked_db):

    mocked_cursor = mocked_db.return_value.cursor.return_value

    description_mock = mock.PropertyMock()
    type(mocked_cursor).description = description_mock

    fetchall_return_one = [('shanky', '347539593')]

    fetchall_return_two = [('nirma', 12313)]

    descriptions = [
        [['name'],['phone']],
        [['name'],['id']]
    ]

    mocked_cursor.fetchall.side_effect = [fetchall_return_one, fetchall_return_two]

    description_mock.side_effect = descriptions

    ret = app.my_function()

    # assert whether called with mocked side effect objects
    mocked_db.assert_has_calls(mocked_cursor.fetchall.side_effect)

    # assert db query count is 2
    assert mocked_db.return_value.cursor.return_value.execute.call_count == 2

    # first query
    query1 = """
            SELECT name,phone FROM customer WHERE name='shanky'
            """
    assert mocked_db.return_value.cursor.return_value.execute.call_args_list[0][0][0] == query1

    # second query
    query2 = """
            SELECT name,id FROM product WHERE name='soap'
            """
    assert mocked_db.return_value.cursor.return_value.execute.call_args_list[1][0][0] == query2

    # assert the data of response
    assert ret == {'name' : 'nirma', 'id' : 12313}

除此之外,如果查询中有动态参数,也可以通过以下方法断言。

assert mocked_db.return_value.cursor.return_value.execute.call_args_list[0][0][1] = (parameter_name,)

因此,当执行第一个查询时,call_args_list[0][0][0]处的cursor.execute(query,(parameter_name,))查询可以获取并断言,在call_args_list[0][0][1]处可以获得第一个参数parameter_name。类似地增加索引,可以获取并断言所有其他参数和不同的查询。

关于Python 使用 pytest_mock 在函数中模拟多个查询,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58873971/

相关文章:

python - 在 mongoDB 中求和数组大小字段的最佳方法

python - 在 Python 中,赋值运算符在类方法定义中作为默认值传递时是否访问类或实例变量?

unit-testing - 如何在Dart中对大量异步流程进行单元测试?

python - 如何手动触发共享 pytest fixture 的重新执行?

python - 如何读取 Python 字符串中的空格,存储它们的确切位置,然后将它们重新插入到新字符串中的这些位置?

python - request.body 中的 Django POST 数据但不能存储在变量中

python - 尝试使用 mysqldump 导出数据库时,是什么导致 subprocess.call 输出空白文件?

python - 尝试将列表的索引绘制为直方图时的“TypeError: list indices must be integers, not str”

python - 使用 yield from 时协程在哪个事件循环中运行?

JQuery 模拟