• Python实用库使用与浅析系列一:httmock


    介绍

    这个系列的第一篇文章,介绍一下httmook库使用和原理,代码只有200多行,实现的很巧妙。

    应用场景:有时会需要调用外部接口,拿到返回数据用以满足当前的测试任务的需求。但是当外部接口不可用,或者没有提供测试用环境时,就需要mock接口。

    pypi链接:https://pypi.org/project/httmock/

    安装:pip install httmock

    使用

    httpmook提供两个装饰器接口:

    • urlmatch
    • all_requests

    urlmatch拦截匹配url的请求

    from httmock import urlmatch, HTTMock
    import requests
    
    @urlmatch(netloc=r'(.*.)?google.com$')
    def google_mock(url, request):
        return 'Feeling lucky, punk?'
    
    with HTTMock(google_mock):
        r = requests.get('http://google.com/')
    print r.content  # 'Feeling lucky, punk?'

    all_requests拦截所有请求:

    from httmock import all_requests, HTTMock
    import requests
    
    @all_requests
    def response_content(url, request):
        return {'status_code': 200,
                'content': 'Oh hai'}
    
    with HTTMock(response_content):
        r = requests.get('https://foo_bar')
    
    print r.status_code
    print r.content

    如何构造返回数据:

    from httmock import all_requests, response, HTTMock
    import requests
    
    @all_requests
    def response_content(url, request):
        headers = {'content-type': 'application/json',
                   'Set-Cookie': 'foo=bar;'}
        content = {'message': 'API rate limit exceeded'}
        return response(403, content, headers, None, 5, request)
    
    with HTTMock(response_content):
        r = requests.get('https://api.github.com/users/whatever')
    
    print r.json().get('message')
    print r.cookies['foo']

    原理

    我以第一个示例来说明:

    通过with语实例一个上下文解析器的类的实例,并传入带有装饰器(urlmatch)的返回数据构造函数google_mock,这是我们通过下面几行代码能看到的:

    from httmock import urlmatch, HTTMock
    import requests
    
    @urlmatch(netloc=r'(.*.)?google.com$')
    def google_mock(url, request):
        return 'Feeling lucky, punk?'
    
    with HTTMock(google_mock):
        r = requests.get('http://google.com/')
    print r.content  # 'Feeling lucky, punk?'

    HTTMook的初始化函数,初始化被装饰的函数google_mock

        def __init__(self, *handlers):
            self.handlers = handlers

    上下文解析器的__enter__做了什么:

        def __enter__(self):
            self._real_session_send = requests.Session.send
            self._real_session_prepare_request = requests.Session.prepare_request
    
            for handler in self.handlers:
                handler_clean_call(handler)
    
            #制造假的send函数
            def _fake_send(session, request, **kwargs):
                response = self.intercept(request, **kwargs)
                if isinstance(response, requests.Response):
                    # this is pasted from requests to handle redirects properly:
                    kwargs.setdefault('stream', session.stream)
                    kwargs.setdefault('verify', session.verify)
                    kwargs.setdefault('cert', session.cert)
                    kwargs.setdefault('proxies', session.proxies)
    
                    allow_redirects = kwargs.pop('allow_redirects', True)
                    stream = kwargs.get('stream')
                    timeout = kwargs.get('timeout')
                    verify = kwargs.get('verify')
                    cert = kwargs.get('cert')
                    proxies = kwargs.get('proxies')
    
                    gen = session.resolve_redirects(
                        response,
                        request,
                        stream=stream,
                        timeout=timeout,
                        verify=verify,
                        cert=cert,
                        proxies=proxies)
    
                    history = [resp for resp in gen] if allow_redirects else []
    
                    if history:
                        history.insert(0, response)
                        response = history.pop()
                        response.history = tuple(history)
    
                    session.cookies.update(response.cookies)
    
                    return response
    
                return self._real_session_send(session, request, **kwargs)
    
            def _fake_prepare_request(session, request):
                """
                Fake this method so the `PreparedRequest` objects contains
                an attribute `original` of the original request.
                """
                prep = self._real_session_prepare_request(session, request)
                prep.original = request
                return prep
    
            #替换requests的send与prepare_request函数
            requests.Session.send = _fake_send
            requests.Session.prepare_request = _fake_prepare_request
    
            return self

    1、首先保存了requests.Session.send与requests.Session.prepare_request

    2、handler_clean_call(handler),对handlers做了预处理

    3、制造替换函数,_fake_send与_fake_prepare_request,并替换requests中原始的函数,作为一门动态语言的优势现在体现出来了:

            #替换requests的send与prepare_request函数
            requests.Session.send = _fake_send
            requests.Session.prepare_request = _fake_prepare_request

    _fake_send函数最重要的代码:

    response = self.intercept(request, **kwargs)

    intercept函数的作用:执行handel函数,拿到构造的返回数据:

     def intercept(self, request, **kwargs):
            url = urlparse.urlsplit(request.url)
            res = first_of(self.handlers, url, request)if isinstance(res, requests.Response):return res
            elif isinstance(res, dict):return response(res.get('status_code'),
                                res.get('content'),
                                res.get('headers'),
                                res.get('reason'),
                                res.get('elapsed', 0),
                                request,
                                stream=kwargs.get('stream', False),
                                http_vsn=res.get('http_vsn', 11))
            elif isinstance(res, (text_type, binary_type)):return response(content=res, stream=kwargs.get('stream', False))
            elif res is None:return None
            else:raise TypeError(
                    "Dont know how to handle response of type {0}".format(type(res)))

    最后执行handle的函数:

    也可以看到google_mock(url, request)的两个参数是如何传入的,这是由于_fake_send替换掉requests运行时态的send包,在执行过程中_fake_send拿到request这个实例封装参数。

    def first_of(handlers, *args, **kwargs):
        for handler in handlers:
            res = handler(*args, **kwargs)
            if res is not None:
                return res

    上下文解析器的__exit__做了什么:

    替换掉运行时的send与prepare函数:

        def __exit__(self, exc_type, exc_val, exc_tb):
            #恢复
            requests.Session.send = self._real_session_send
            requests.Session.prepare_request = self._real_session_prepare_request
  • 相关阅读:
    [git 学习篇]自己在github创建一个远程服务器创库
    [git 学习篇]远程创库
    [git 学习篇]删除文件
    [git 学习篇] git checkout 撤销修改
    [git 学习篇]git管理的是修改,并非文件
    log4net面面观之Repository
    log4net面面观之工作原理
    asp.net获取当前页面文件名,参数,域名等方法。统一session验证和权限验证的方法
    C#使用Log4Net记录日志
    SharePoint 2010中的客户端AJAX应用——ASP.NET AJAX模板
  • 原文地址:https://www.cnblogs.com/-wenli/p/14001207.html
Copyright © 2020-2023  润新知