1

如何使用 pytest 模拟测试以下函数?

import http.client

def get_response(req_type, host, sub_domain, payload=None, headers=None,
                 body=None):

    conn = http.client.HTTPSConnection(host)
    conn.request(req_type, sub_domain, headers=headers, body=payload)
    response = conn.getresponse()

    if response.status != 200:
        raise Exception('invalid http status ' + str(response.status)
                        + ',detail body:' + response.read().decode("utf-8"))

    data = response.read().decode("utf-8")
    conn.close()

    return data

通过使用 pytest_mock 库并参考此处的代码,我能够对get_response()用于执行某些操作的其他函数进行单元测试。因此,如果我们甚至需要使用 pytest_mock 库来执行单元测试,那就没问题了get_response

话虽如此,到目前为止我所看到的解决方案都是针对requests&unittest库的

我想避免创建一个 http 服务器或烧瓶服务器这个基于模拟的单元测试。

Pytest 文档 似乎建议我需要一个补丁来进行http.client.HTTPSConnection单元测试conn.request()。是这样吗?conn.getresponse()get_response()

一个最小的工作示例会有所帮助。提前致谢!

4

1 回答 1

1

这是一个最小的工作示例(假设您的方法放在 中http_call.py):

from http_call import get_response


def test_get_response(mocker):
  # mocked dependencies
  mock_HTTPSConnection = mocker.MagicMock(name='HTTPSConnection')
  mocker.patch('http_call.http.client.HTTPSConnection', new=mock_HTTPSConnection)
  mock_HTTPSConnection.return_value.getresponse.return_value.status = 200
  mock_HTTPSConnection.return_value.getresponse.return_value.read.return_value.decode.return_value = "some html goes here"

  # act
  data = get_response("GET", "www.google.com", '/', headers={})

  # assert
  assert "some html goes here" == data

该示例解释:

  1. 您模拟了 https 连接的“根”,即HTTPSConnection类。然后你必须确保你的响应是 200 并返回一些你可以断言的数据。
  2. 然后调用函数并获取输出
  3. 然后,您可以使用已模拟的预定义硬编码值断言输出

你可以在这个测试函数中做一些额外的事情:

添加断言,使用我的pytest-mock-generator库夹具,如下所示:

def test_get_response(mocker, mg):
    # mocked dependencies
    mock_HTTPSConnection = mocker.MagicMock(name='HTTPSConnection')
    mocker.patch('http_call.http.client.HTTPSConnection', new=mock_HTTPSConnection)
    mock_HTTPSConnection.return_value.getresponse.return_value.status = 200
    mock_HTTPSConnection.return_value.getresponse.return_value.read.return_value.decode.return_value = "some html goes here"

    # act
    data = get_response("GET", "www.google.com", '/', headers={})

    # assert
    assert "some html goes here" == data

    # this code generates extra asserts
    mg.generate_asserts(mock_HTTPSConnection)

这将生成输出(打印到控制台并复制到剪贴板):

assert 1 == mock_HTTPSConnection.call_count
mock_HTTPSConnection.assert_called_once_with('www.google.com')
mock_HTTPSConnection.return_value.request.assert_called_once_with('GET', '/', body=None, headers={})
mock_HTTPSConnection.return_value.getresponse.assert_called_once_with()
mock_HTTPSConnection.return_value.getresponse.return_value.read.assert_called_once_with()
mock_HTTPSConnection.return_value.getresponse.return_value.read.return_value.decode.assert_called_once_with('utf-8')
mock_HTTPSConnection.return_value.close.assert_called_once_with()

这些额外的断言可以帮助您确保使用正确的参数调用函数。

我的库可以做的另一件事是通过分析您的代码来生成初始模拟,如下所示:

def test_get_response(mocker, mg):
    mg.generate_uut_mocks(get_response)

你会得到这个输出:

# mocked dependencies
mock_HTTPSConnection = mocker.MagicMock(name='HTTPSConnection')
mocker.patch('http_call.http.client.HTTPSConnection', new=mock_HTTPSConnection)
mock_Exception = mocker.MagicMock(name='Exception')
mocker.patch('http_call.Exception', new=mock_Exception)
mock_str = mocker.MagicMock(name='str')
mocker.patch('http_call.str', new=mock_str)

显然不需要嘲笑Exceptionand str,所以你可以放弃这些建议。

于 2021-09-09T09:27:55.630 回答