【问题标题】:How to mock data as request.Response type in python如何在python中将数据模拟为request.Response类型
【发布时间】:2019-01-11 17:19:25
【问题描述】:

我想写一些测试用例来练习 object_check in isinstance(obj, requests.Response) 逻辑。在我创建 Mock 数据作为 requests.post 的返回值之后。模拟数据的类型始终是 Mock 类。这样,我如何重写模拟数据,以便模拟数据可以是 requests.Response 的类型?这样我就可以锻炼线d = obj.json()

from unittest.mock import patch, Mock
import unittest
import requests
from requests.exceptions import HTTPError
import pytest
def object_check(obj):
    if isinstance(obj, bytes):
        d = ujson.decode(obj.decode())
    elif isinstance(obj, requests.Response):
        d = obj.json()
    else:
        raise ValueError('invalid type')
    return d

def service_post(params):
    """
    trivial function that does a GET request
    against google, checks the status of the
    result and returns the raw content
    """
    url = "https://www.iamdomain.com"
    params = {'number': 1234, 'user_id': 1, 'name': 'john'}
    resp = requests.post(url, data=params)
    return object_check(resp)

@patch.object(requests, 'post')
def test_service_post(mock_request_post):
    data = {'number': 0000, 'user_id': 0, 'name': 'john'}
    def res():
        r = Mock()
        r.status_code.return_value = 200
        r.json.return_value = data
        return r
    mock_request_post.return_value = res()
    assert data == service_post(data)

【问题讨论】:

    标签: python python-3.x pytest python-unittest python-unittest.mock


    【解决方案1】:

    你可以这样做:

    @patch.object(requests, 'post')
    def test_service_post(mock_request_post):
        data = {'number': 0000, 'user_id': 0, 'name': 'john'}
        def res():
            r = requests.Response()
            r.status_code = 200
            def json_func():
                return data
            r.json = json_func
            return r
        mock_request_post.return_value = res()
        assert data == service_post(data)
    

    当我在本地运行它时,测试通过了。 请注意,Mock 是一种迷你气味。

    我曾经是Mock 的忠实粉丝。不过,随着我成长为一名开发人员,我真的尽量避免它。它可能会诱使您进行一些非常糟糕的设计,并且它们可能很难维护(尤其是因为您正在修改 Mock 以保存返回值)。 Mock 也可能造成错误的安全感(即使 Web 服务发生巨大变化,您的测试仍将继续通过,因此您可能会在产品中爆炸)。我不认为你在这里真的需要它。两种选择:

    1. 您可以点击任何您想要点击的服务,并使用pickle 序列化(保存)该响应,然后存储到磁盘(将其保存在您的测试套件中)。然后让您的单元测试将其读回并使用实际的响应对象。您仍然需要 patch 而不是 requests.post,但至少会为您排列好返回值,并且您不必随着需求/应用程序的增长而添加或修改它们。
    2. 刚刚上网。完全忘记patch:只需在测试中进行 POST 并检查响应。当然,这可能会很慢,并且只有在您有互联网的情况下才有效。你会得到愚蠢的纯粹主义者,他们会告诉你永远不要在单元测试中这样做。如果您遇到其中一个纯粹的人,也许可以将其转移到集成测试中。但说真的,没有什么可以替代你在 prod 中实际要做的事情。这样做的好处是,如果 Web 服务发生变化,那么您将立即了解它并可以修复您的代码。缺点是它可能会减慢您的测试套件的速度,并且它可能是一个不可靠的测试(如果 Web 服务关闭,您的测试将失败......但实际上知道这一点可能会很好)。

    我建议如果 web 服务不稳定(即容易改变),使用选项 2。否则,使用选项 1。或者做一些组合(Mockpatch 进行单元测试,然后点击服务在集成测试中)。只有你可以决定!

    HTH,祝你好运!

    【讨论】:

    • 谢谢你..这是一个很好的建议。对于选项 1,对我来说“用泡菜序列化(保存)响应”的最佳方法是什么?喜欢你的代码示例?
    • pickle.dump(your_object, your_file_descriptor)pickle.load("your_filename")。你可以在这里看到更多:stackoverflow.com/questions/4530611/… 和泡菜文档在这里:docs.python.org/3/library/pickle.html。您需要实际访问 Web 服务,然后在 elif isinstance(obj, requests.Response): 之后插入转储逻辑。同样,当您进行测试时,您从磁盘读回 requests.Response 对象并将其传递到您想要运行需要它作为依赖项的单元测试的任何地方
    • 太棒了!很高兴能帮上忙。
    • 听到有人指出模拟一些你应该在它得到刺激之前检查的东西是多么愚蠢,这真的令人鼓舞。我觉得我是一个疯狂的人,认为测试函数中的 Live API 调用是可以的。
    • @DaveLiu 不,你是理智的人,我喜欢马特不幸地使用从脚趾到头顶的代码。那么如果你重写 90% 的代码,让测试通过的唯一解决办法就是更新模拟路径,从现在开始,没关系的测试将毫无意义。
    【解决方案2】:

    在实例化模拟时使用spec 参数:

    >>> from unittest.mock import Mock
    >>> from requests import Response
    >>> m = Mock(spec=Response)
    >>> m.__class__
    requests.models.Response
    >>> isinstance(m, Response)
    True
    

    另请注意,r.status_code.return_value = 200 不适用于规范;而是直接设置值:

    r.status_code = 200
    

    【讨论】:

    • 但是isinstance(m, request.Response) == 假?这是我的问题
    • 我不确定你的问题是什么。没有这样的课程requests.Response(除了你写的request.Response;我认为这只是一个错字)。有一个类 requests.models.Response 通过__all__requests.__init__.py导出。所以isinstance(m, requests.Response) 将返回True
    【解决方案3】:

    如果您想模拟textcontent @property 值,请在text 周围使用PropertyMock

    @patch.object(requests, 'post')
    def test_service_post(mock_request_post):
        data = {'number': 0000, 'user_id': 0, 'name': 'john'}
        def res():
            r = requests.Response()
            r.status_code = 200
            type(r).text = mock.PropertyMock(return_value=my_text)  # property mock
            def json_func():
                return data
            r.json = json_func
            return r
        mock_request_post.return_value = res()
        assert data == service_post(data)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-18
      • 2017-07-23
      • 2012-10-27
      • 1970-01-01
      • 2019-01-09
      • 1970-01-01
      相关资源
      最近更新 更多