51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 4144|回复: 1
打印 上一主题 下一主题

[原创] 接口测试之requests库你不知道的那些事?

[复制链接]
  • TA的每日心情
    无聊
    3 天前
  • 签到天数: 1050 天

    连续签到: 1 天

    [LV.10]测试总司令

    跳转到指定楼层
    1#
    发表于 2020-12-25 10:41:50 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    前言
      说到python发送HTTP请求进行接口自动化测试,脑子里第一个闪过的可能就是requests库了,当然python有很多模块可以发送HTTP请求,包括原生的模块http.client,urllib2等,但由于原生的模块过于复杂,使用繁琐,那么requests库就诞生了,它也是现阶段比较流行的接口自动化测试工具之一
      requests是个第三方库,封装了HTTP请求的所有方法,使用方便简单,只需要根据不同的请求方式调用相对应的方法就可以完成发送网络请求的整个过程,那么今天我们就来说说requests库是如何使用的,那么在开始之前我们大致说一下今天的主要内容:
      1. requets如何发送get请求
      2. requests如何发送post请求
      3. params参数和data参数的区别
      4. requests库中Session类的的妙用
      5. 针对get请求和post请求,对requests进行简单封装
      发送get请求
      requests是通过调用get()方法来完成发送get请求的,那么,在掌握requests库如何发送get请求之前,你还应该简单了解一下什么是get请求,你可以参照这篇文章https://www.cnblogs.com/linuxchao/p/linuxchao-http.html
      通常在我们学习一个方法如何使用之前,我们需先知道这个方法需要哪些参数?参数类型是什么? 那么我们就先分析一下get()方法的源码
    1.    1 def get(url, params=None, **kwargs):
    2.    2     r"""Sends a GET request.
    3.    3
    4.    4     :param url: URL for the new :class:`Request` object.
    5.    5     :param params: (optional) Dictionary, list of tuples or bytes to send
    6.    6         in the body of the :class:`Request`.
    7.    7     :param \*\*kwargs: Optional arguments that ``request`` takes.
    8.    8     :return: :class:`Response <Response>` object
    9.    9     :rtype: requests.Response
    10.   10     """
    11.   11
    12.   12     kwargs.setdefault('allow_redirects', True)
    13.   13     return request('get', url, params=params, **kwargs)
    复制代码

    这就是get方法的源码了,你应该能够发现,get()方法只有一个必传的参数url,其他参数都是非必传的,那么其他的参数有什么作用呢?
      params
      对于这个参数,可以是字典,也可以是嵌套元组的列表,基于get请求的特点,请求参数是可以直接跟在URL之后的,以?号开始以key=value的形式传递(多个参数使用&符号进行分割),但是为了明确区分URL和参数,就需要使用params参数传递
      **kwargs:其他一些关键字参数,暂时不做介绍
      接下来我们来看2个简单的实例,体会一下reauets通过get()方法发送一个不带参数的get请求和带参数的请求的过程
      通过get()方法发送get请求访问博客园首页
    1. """
    2.   ------------------------------------
    3.   [url=home.php?mod=space&uid=3428]@time[/url] : 2019/7/11 20:34
    4.   @Auth : linux超
    5.   [url=home.php?mod=space&uid=273702]@file[/url] : requests_blog.py
    6.   @IDE  : PyCharm
    7.   @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
    8.   [url=home.php?mod=space&uid=10310]@QQ[/url]   : 28174043@qq.com
    9.   @GROUP: 878565760
    10.   ------------------------------------
    11.   """
    12.   import requests  # 导入requests模块


    13.   response = requests.get('https://www.cnblogs.com/')  # 发送get请求
    14.   print(response.text)  # 获取响应数据
    15.   响应数据
    16.   <!DOCTYPE html>
    17.   <html lang="zh-cn">
    18.   <head>
    19.       <meta charset="utf-8" />
    20.       <meta name="viewport" content="width=device-width, initial-scale=1" />
    21.       <meta name="referrer" content="always" />
    22.       <title>博客园 - 代码改变世界</title>
    23.           <meta name="keywords" content="开发者,博客园,开发者,程序猿,程序媛,极客,编程,代码,开源,IT网站,Developer,Programmer,Coder,Geek,技术社区" />
    24.               <meta name="description" content="博客园是一个面向开发者的知识分享社区。自创建以来,博客园一直致力并专注于为开发者打造一个纯净的技术交流社区,推动并帮助开发者通过互联网分享知识,从而让更多开发者从中受益。博客园的使命是帮助开发者用代码改变世界。" />
    25.       <link rel="shortcut icon" href="//common.cnblogs.com/favicon.ico" type="image/x-icon" />
    26.       <link rel="Stylesheet" type="text/css" href="/bundles/aggsite.css?v=IlEZk4Ic2eCzcO6r0s4bGm62FAo8VZI-US_0EqUe2Bk1" />
    27.           <link id="RSSLink" title="RSS" type="application/rss+xml" rel="alternate" href="http://feed.cnblogs.com/blog/sitehome/rss" />
    28.       <script src="//common.cnblogs.com/scripts/jquery-2.2.0.min.js" type="text/javascript"></script>
    29.       <script src="/bundles/aggsite.js?v=cE0bqImLWsEG3gZXOupKxj5tj_ukK7pLeSd73DHZOT81" type="text/javascript"></script>
    30.       <script async='async' src='https://www.googletagservices.com/tag/js/gpt.js'></script>
    31.       <script>
    32.           var googletag = googletag || {};
    33.           googletag.cmd = googletag.cmd || [];
    34.   </script>
    复制代码
    这里我只截取了一部分响应数据,响应数据其实是博客园的首页HTML源码
      可以看到只需要一行代码即可完成整个请求过程,通过response.text得到响应数据(其实这个过程和我们在浏览器中输入博客园地址访问是一样的),当然你还可以使用以下的方法获取不同的响应数据

    1. response.headers  # 获取响应头信息
    2.   response.cookies  # 获取返回的cookie
    3.   response.status_code  # 获取状态码
    4.   发送带params参数的get请求
    5.   """
    6.   ------------------------------------
    7.   @Time : 2019/7/11 20:34
    8.   @Auth : linux超
    9.   @File : requests_blog.py
    10.   @IDE  : PyCharm
    11.   @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
    12.   @QQ   : 28174043@qq.com
    13.   @GROUP: 878565760
    14.   ------------------------------------
    15.   """
    16.   import requests

    17.   login_url = r'http://***:8080/futureloan/mvc/api/member/login'  # 接口地址
    18.   login_data = {"mobilephone": "13691579841", "pwd": 123456}  # 接口所需参数
    19.   response = requests.get(url=login_url, params=login_data)  # 发送get请求
    20.   print(response.text)  # 获取响应数据

    21.   print(response.url)
    复制代码
    响应数据
    1.  {"status":1,"code":"10001","data":null,"msg":"登录成功"}
    2.   http://***:8080/futureloan/mvc/api/member/login?mobilephone=13691579841&pwd=123456
    3.   Process finished with exit code 0
    复制代码

     我们通过传递一个字典给params参数,即可完成带参数的get请求,并获取响应数据
      注意
      1.我们日常工作中也最好传递字典作为接口数据,如果数据类型是json则在发送请求的时候需要先转成字典
      2.只要你发送的是get请求,且不想通过url拼接的方式直接传递接口参数,那么只能使用params参数来传递(如果你使用data,json等参数时你会发现,请求并不会成功),因为通过params传递的参数会附加到url后,这也是get请求的特点,因此你需记住:发送get请求时参数只能使用params即可
      以上只是简单的发送get请求的方法,至于如何发送带其他参数的get请求(比如headers,cookies等),还需对get()进一步的研究和实践
      发送post请求
      同样,在开始学习下面的内容之前,仍建议你先了解什么是post请求及其特点,对你学习接下来的内容也会更好理解
      https://www.cnblogs.com/linuxchao/p/linuxchao-http.html
      requests发送post请求是通过post()方法来实现的,那么我们还是先看一下它的源码
    1. 1 def post(url, data=None, json=None, **kwargs):
    2.    2     r"""Sends a POST request.
    3.    3
    4.    4     :param url: URL for the new :class:`Request` object.
    5.    5     :param data: (optional) Dictionary (will be form-encoded), bytes, or file-like object to send in the body of the :class:`Request`.
    6.    6     :param json: (optional) json data to send in the body of the :class:`Request`.
    7.    7     :param \*\*kwargs: Optional arguments that ``request`` takes.
    8.    8     :return: :class:`Response <Response>` object
    9.    9     :rtype: requests.Response
    10.   10     """
    11.   11
    12.   12     return request('post', url, data=data, json=json, **kwargs)
    复制代码

     通过源码我们可以发现,post()和get()一样,仅有一个url参数是必传的,其他仍然可以不传递,但是其中data和json参数却是post()方法中最重要的参数,下面就说一下何时使用data,何时使用json
      data
      大多数post请求的接口默认支持参数类型Content-Type为application/x-www-form-urlencoded, 它告诉我们请求的接口参数需要传递一个form表单,那么我们往往是通过构造一个字典来传递form表单的,
      所以当我们向服务器提交form表单时就可以使用data参数,它会接收一个字典类型的数据,存放到请求体中,然后发送给服务器(参数需是字典类型)
      json
      首先你访问的接口需要支持content_type为application/json格式的数据类型,那么你就可以通过json这个参数来传递接口参数(参数可以是字典也可以是json类型)
      下面我们来看一个发送post请求,使用data参数的实例
    1. """
    2.   ------------------------------------
    3.   @Time : 2019/7/12 10:22
    4.   @Auth : linux超
    5.   @File : requests_blog.py
    6.   @IDE  : PyCharm
    7.   @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
    8.   @QQ   : 28174043@qq.com
    9.   @GROUP: 878565760
    10.   ------------------------------------
    11.   """
    12.   import requests

    13.   login_url = r'http://****:8080/futureloan/mvc/api/member/login'  # 接口地址
    14.   login_data = {"mobilephone": "13691579841", "pwd": 123456} # 接口所需参数
    15.   response = requests.post(url=login_url, data=login_data)  # 发送post请求
    16.   print(response.text)  # 获取响应数据
    17.   print(response.url)
    18.   print(response.status_code)
    19.   响应数据
    20.   {"status":1,"code":"10001","data":null,"msg":"登录成功"}
    21.   http://***:8080/futureloan/mvc/api/member/login
    22.   200

    23.   Process finished with exit code 0
    24.   使用json参数实例
    25.   """
    26.   ------------------------------------
    27.   @Time : 2019/7/12 10:22
    28.   @Auth : linux超
    29.   @File : requests_blog.py
    30.   @IDE  : PyCharm
    31.   @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
    32.   @QQ   : 28174043@qq.com
    33.   @GROUP: 878565760
    34.   ------------------------------------
    35.   """
    36.   import requests

    37.   login_url = r'http://****:8080/futureloan/mvc/api/member/login'  # 接口地址
    38.   login_data = {"mobilephone": "13691579841", "pwd": 123456} # 接口所需参数
    39.   response = requests.post(url=login_url, json=login_data)  # 发送post请求
    40.   print(response.text)  # 获取响应数据
    41.   print(response.url)
    42.   print(response.status_code)
    43.   响应数据
    44.   {"status":0,"code":"20103","data":null,"msg":"手机号不能为空"}
    45.   http://***/futureloan/mvc/api/member/login
    46.   200

    47.   Process finished with exit code 0
    复制代码

    可以看到使用data参数和json参数,获取到的返回结果是不一样的,因为我这里使用的接口不支持application/json数据格式,所以当你使用json参数传递参数时,服务器是无法解析数据的,也就不会返回正确的结果了
      所以对于何时使用data参数,何时使用json参数,还需要根据实际的接口所支持的数据类型进行选择
      params和data区别
      上面已经说过get请求中的params参数和post请求中的data参数,那么这两个参数到底有什么区别呢?
      1. 发送get请求时,由于get请求没有请求体,请求参数只能跟在url地址后的,而且服务器也只能通过解析url获得请求的参数,因此get()方法发送get请求时只能使用params参数,它会把请求的参数默认追加到url地址后面
      2. 通常情况下用户需要提交某些数据时,发送的请求一般都为post请求,post请求会提交一个form表单,那么我们就可以构造一个字典格式的数据,使用data参数传递,由于post请求是有请求体的,而且请求参数就存放在请求体中,服务器也只能通过解析请求体中内容而获得请求的参数,所以post请求不能使用params传递接口参数,只能使用data,json,file等, data参数会把请求参数放到请求体中
      Session类的妙用
      实际工作中,我们会经常遇到需要保持某一个状态,才能测试后续的接口,比如说:充值接口,那么需要用户先登录,且一直保持登录状态才能进行充值,那么对于这种情况该怎么解决呢?这就要用到requests库中的Session类了,Session可以保持请求的状态,像我们访问某个网站一样,我们只要登录后就可以浏览该网站上的任意页面,先看下面实例
    1. """
    2.   ------------------------------------
    3.   @Time : 2019/7/12 10:22
    4.   @Auth : linux超
    5.   @File : requests_blog.py
    6.   @IDE  : PyCharm
    7.   @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
    8.   @QQ   : [email]28174043@qq.com[/email]
    9.   @GROUP: 878565760
    10.   ------------------------------------
    11.   """
    12.   import requests

    13.   login_url = r'http://***:8080/futureloan/mvc/api/member/login'  #  登录接口地址
    14.   login_data = {"mobilephone": "13691579841", "pwd": 123456} # 接口所需参数
    15.   response_login = requests.post(url=login_url, data=login_data)  # 发送post请求 登录
    16.   print(response_login.text)
    17.   recharge_url = r'http://***:8080/futureloan/mvc/api/member/recharge'  # 充值接口地址
    18.   recharge_data = {"mobilephone": "13691579841", "amount": 10000.00}  # 接口所需参数
    19.   response_recharge = requests.post(url=recharge_url, data=recharge_data)  # 发送请求,开始充值
    20.   print(response_recharge.text)
    复制代码
     执行结果
    1. {"status":1,"code":"10001","data":null,"msg":"登录成功"}
    2.   {"status":0,"code":null,"data":null,"msg":"抱歉,请先登录。"}

    3.   Process finished with exit code 0
    复制代码
     可以发现,我们之前都已经登录过了,但是充值时却失败了,原因就是直接使用reauests来发送请求时,并不会保持当前的状态(这也是HTTP请求的缺陷),现在我们使用Session对像再次发送充值请求,修改代码
    1. """
    2.   ------------------------------------
    3.   @Time : 2019/7/12 10:22
    4.   @Auth : linux超
    5.   @File : requests_blog.py
    6.   @IDE  : PyCharm
    7.   @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
    8.   @QQ   : [email]28174043@qq.com[/email]
    9.   @GROUP: 878565760
    10.   ------------------------------------
    11.   """
    12.   import requests


    13.   request = requests.Session()  # 初始化Session
    14.   login_url = r'http://***:8080/futureloan/mvc/api/member/login'  #  登录接口地址
    15.   login_data = {"mobilephone": "13691579841", "pwd": 123456} # 接口所需参数
    16.   response_login = request.request(method='post', url=login_url, data=login_data)  # 发送post请求 登录
    17.   print(response_login.text)
    18.   recharge_url = r'http://***:8080/futureloan/mvc/api/member/recharge'  # 充值接口地址
    19.   recharge_data = {"mobilephone": "13691579841", "amount": 10000.00}  # 接口所需参数
    20.   response_recharge = request.request(method='post', url=recharge_url, data=recharge_data)  # 发送请求,开始充值
    21.   print(response_recharge.text)
    复制代码

    执行结果

    1. {"status":1,"code":"10001","data":null,"msg":"登录成功"}
    2.   {"status":1,"code":"10001","data":
    3.   {"id":5451,"regname":"test","pwd":"E10ADC3949BA59ABBE56E057F20F883E","mobilephone":"13691579841","leaveamount":"15000.00","type":"1","regtime":"2019-05-26 19:08:44.0"},

    4.   "msg":"充值成功"}

    5.   Process finished with exit code 0
    复制代码
      可以发现,我们改用Session对象来发送充值请求就成功了。那这是什么原因呢?
      简单来说,当我们第一次请求服务器时,获取的响应信息会包含一个set-cookie的字段,保存了我们登录的cookies信息,如果我们想保持这个状态,那么再次访问服务器时就需要带上这个cookies传递给服务器,才能保持这个状态。
      那么我们使用Session对象发送请求时,Session会自动帮我们完成上述的过程,Session会自动把cookies的信息传递给服务器,而无需我们在请求参数中手动添加cookies,这样就保持了登录的状态,后续的依赖操作都可以正常执行了
      reqests简单封装
      有人会问,requests库已经封装的很好了,直接用就行了,为啥还要自己封装一次?
      第一. 通过封装,我可以直接把所有的请求参数统一使用字典来传递
      比如,我们接口请求需要的数据也就是测试数据往往会保存在excel表里面,那么我们取到后是字符串类型,字符串是无法作为请求参数传递的,所以我每次都要做数据转换,再传递给接口,为了节省这个过程,我只需要把这个过程封装到我的requests里即可,每次取数据后自动给我处理
      第二. 当我想保持某个状态时,不想每次都初始化一个Session对象,那么我可以把它封装到我的requests里面,以后直接调用即可
      下面来看封装的代码
    1.   1 """
    2.    2 ------------------------------------
    3.    3 @Time : 2019/7/12 16:14
    4.    4 @Auth : linux超
    5.    5 @File : sendrequests.py
    6.    6 @IDE  : PyCharm
    7.    7 @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
    8.    8 @QQ   : 28174043@qq.com
    9.    9 @GROUP: 878565760
    10.   10 ------------------------------------
    11.   11 """
    12.   12 import json
    13.   13 import requests
    14.   14
    15.   15
    16.   16 class HttpRequests(object):
    17.   17     """
    18.   18     eg: request = HttpRequests()
    19.   19         response = request(method, url, data)
    20.   20         or
    21.   21         response = request.send_request(method, url, data)
    22.   22         print(response.text)
    23.   23     """
    24.   24     def __init__(self):
    25.   25         self.session = requests.Session()
    26.   26
    27.   27     def send_request(self, method, url, params_type='form', data=None, **kwargs):
    28.   28         method = method.upper()
    29.   29         params_type = params_type.upper()
    30.   30         if isinstance(data, str):
    31.   31             try:
    32.   32                 data = json.loads(data)
    33.   33             except Exception:
    34.   34                     data = eval(data)
    35.   35         if 'GET' == method:
    36.   36             response = self.session.request(method=method, url=url, params=data, **kwargs)
    37.   37         elif 'POST' == method:
    38.   38             if params_type == 'FORM':  # 发送表单数据,使用data参数传递
    39.   39                 response = self.session.request(method=method, url=url, data=data, **kwargs)
    40.   40             elif params_type == 'JSON':  # 如果接口支持application/json类型,则使用json参数传递
    41.   41                 response = self.session.request(method=method, url=url, json=data, **kwargs)
    42.   42             else:  # 如果接口需要传递其他类型的数据比如 上传文件,调用下面的请求方法
    43.   43                 response = self.session.request(method=method, url=url, **kwargs)
    44.   44         # 如果请求方式非 get 和post 会报错,当然你也可以继续添加其他的请求方法
    45.   45         else:
    46.   46             raise ValueError('request method "{}" error ! please check'.format(method))
    47.   47         return response
    48.   48
    49.   49     def __call__(self, method, url, params_type='form', data=None, **kwargs):
    50.   50         return self.send_request(method, url,
    51.   51                                  params_type=params_type,
    52.   52                                  data=data,
    53.   53                                  **kwargs)
    54.   54
    55.   55     def close_session(self):
    56.   56         self.session.close()
    57.   57         try:
    58.   58             del self.session.cookies['JSESSIONID']
    59.   59         except:
    60.   60             pass
    61.   61
    62.   62
    63.   63 request = HttpRequests()
    64.   64
    65.   65
    66.   66 if __name__ == '__main__':
    67.   67     pass
    复制代码
    这个封装只针对了get请求和post请求,当然你也可以把put,delete等请求添加在32行代码后面,实现更多的请求方式
      解释一下30-34行代码: 这几行数据是为了把json和字符串类型的数据转换为字典的格式(通过使用字典传递接口参数)且可以处理一些特殊的形式,比如下面这样的格式
      '{"mobilephone": None, "pwd": null}' # 字符串类型的,但是即不是json形式的字符串,也不是字典类型的字符串,因为字典里面没有null
      封装测试
      现在我们使用封装好的方法来测试一下发送登录和充值接口的请求
    1. """
    2.   ------------------------------------
    3.   @Time : 2019/7/12 16:16
    4.   @Auth : linux超
    5.   @File : test_requests.py
    6.   @IDE  : PyCharm
    7.   @Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
    8.   @QQ   : 28174043@qq.com
    9.   @GROUP: 878565760
    10.   ------------------------------------
    11.   """
    12.   from sendrequests import request
    13.   import unittest


    14.   class TestRequests(unittest.TestCase):

    15.       # 登录接口地址
    16.       login_url = r'http://***:8080/futureloan/mvc/api/member/login'
    17.       # 登录接口测试数据
    18.       login_test_value = '{"mobilephone": "13691579841", "pwd": 123456}'

    19.       # 充值接口地址
    20.       recharge_url = r'http://***:8080/futureloan/mvc/api/member/recharge'
    21.       # 充值接口测试数据
    22.       recharge_test_value = {"mobilephone": "13691579841", "amount": 10000.00}

    23.       def test_login_api(self):
    24.           """登录接口测试用例"""
    25.           response = request('get', url=self.login_url, data=self.login_test_value)
    26.           self.assertTrue(response.json()["code"] == "10001")
    27.           print("登录接口测试通过")

    28.       def test_recharge_api(self):
    29.           """充值接口测试用例"""
    30.           response = request('get', url=self.login_url, data=self.login_test_value)
    31.           try:
    32.               # 充值接口需要先登录,才能充值
    33.               self.assertTrue(response.json()["code"] == '10001')
    34.           except AssertionError as e:
    35.               print('登录失败!')
    36.               raise e
    37.           else:
    38.               response = request('post', url=self.recharge_url, data=self.recharge_test_value)
    39.               self.assertTrue(response.json()["code"] == "10001")
    40.               print("充值接口测试通过")

    41.   if __name__ == '__main__':
    42.   unittest.main()
    复制代码
    测试结果
      登录接口测试通过
      ..
      充值接口测试通过
    1. ----------------------------------------------------------------------
    2.   Ran 2 tests in 0.570s

    3.   OK

    4.   Process finished with exit code 0
    复制代码
    ok,测试代码执行通过,说明我们的封装没有啥问题, 且可以正常发送get和post请求,也可以解决测试数据问题和需要接口依赖的问题
      总结
      最后我们再来总结一下本文涉及到的所有的知识点和你需要掌握的
      1. requests发送get请求和post请求的方法
    1. get(url, params=None, **kwargs)
    2.   post(url, data=None, json=None, **kwargs)
    复制代码
    2. parmas参数和data参数的区别
        由于get请求无请求体,post请求有请求体
        使用params参数时,默认会把参数附加到url后面,所以发送get请求时应使用params参数
        使用data参数时,参数会存放到请求体中,所以发送post请求时不能使用params,应使用data,除非接口及支持get又支持post,同样get请求也不能使用data参数
      3. 如何使用Seesion解决接口保持状态的问题
        初始化Session实例,通过这个实例调用request()方法发送请求
      4. 最重要的一个封装方法,并掌握这个封装该如何使用
        主要针对get和post请求的接口










    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏
    回复

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-11-24 07:39 , Processed in 0.066479 second(s), 22 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表