接口自动化--requests库的使用

一、Requests库部署与常用函数讲解

1.安装

安装命令:

pip install requests

验证命令:

pip show requests

2.Requests库简介

Requests 是用Python语言编写,基于 urllib,采用 Apache2 Licensed 开源协议的 HTTP 库。它比

urllib 更加方便,可以节约我们大量的工作,完全满足 HTTP 测试需求。

官网介绍:

https://cn.python-requests.org/zh_CN/latest/

3.requests库常用的7种方法

requests.requests()

requests.options(‘https://github.com/timeline.json’) #OPTIONS请求(跨域预检请求)

requests.get(‘https://github.com/timeline.json’) #GET请求

requests.post(“http://httpbin.org/post”) #POST请求

requests.put(“http://httpbin.org/put”) #PUT请求(提交修改全部的数据)

requests.delete(“http://httpbin.org/delete”) #DELETE请求


requests.head(“http://httpbin.org/get”) #HEAD请求

requests.patch(“http://httpbin.org/get”) #PATCH请求(提交修改部分数据)

4. requests() 方法的参数详解

【一般来说会用这个方法来进行封装】,因为其中有对应的方法,这篇的最后会进行讲解

requests.requests(method, url, **kwargs)

method:

请求方式:GET, PUT, POST, HEAD, PATCH, Delete, OPTIONS 7种方式

url:网络链接

**kwargs: (13个可选参数):

params

字典或者字节序列,作为参数增加到url中

json

JSON格式的数据,作为requests的内容

headers

字典,HTTP定制头

data

是第二个控制参数,向服务器提交数据

cookies

字典或CookieJar, Requests中的cookie

auth

元组,支持HTTP认证功能

files

字典类型,传输文件

timeout

设置的超时时间,秒为单位

proxies

字典类型,设定访问代理服务器,可以增加登录认证

allow_redirects

True/False,默认为True, 重定向开关

stream:

True/False,默认为True,获取内容立即下载开关 verity: True/False,默认为True, 认证SSL证书

cert:

本地SSL证书路径

5. get()方法的参数

参数说明:

requests.get(url, params=None, kwargs)
url: 拟获取页面的url链接
params: url中的额外参数,字典或字节流,可选择
**kwargs:12个控制访问的参数,就是requests中除params参数

若需要传请求参数, 可直接在 url 后面添加,也可以在调用 get() 方法时通过关键字 params传入,

需要注意的是params 需要传入 dict(字典)类型。

import requests
"""
get
"""
# 通过url直接加上请求参数,与通过params传参效果是一样的
response = requests.get(url='http://www.baidu.com/s?wd=requests模块')
# 通过params传参
response2 = requests.get(url='http://www.baidu.com/s',
                         params={
                             "wd": 'requests模块'
                         }
                         )
print(response)  # 打印状态码
print(response2)  # 打印状态码
print(response2.text)  # 获取响应内容

6. post()方法

requests.post(url, data=None, json=None, **kwargs)

参数说明

dict 类型    使用关键字data传参,
json 类型    则为使用关键字json传参。若无需传参可不传

代码:

register_url = "https://v2.diancang.site/v2/verify"
# 添加请求头,需要就传
header = {
    "Content-Type": "application/json",
}
# json类型的参数
json = {
    "mobile": "15100424463",
    "code": "796305"
}
response = requests.post(url=register_url, json=json, headers=header)
print(response.json())

传参说明:

一般来说之前的要判断传的参数类型是什么?在查看请求头,中的 Content-Type 来进行判断要传什么参数:

dict:
"Content-Type: application/x-www-form-urlencoded"
    用关键字 data 去传dcit类型的参数    
import json
r = requests.post(url, data=json.dumps(body))
print(r.text)
json:
"Content-Type": "application/json"
    json类型的参数一定要使用关键字json去传递。

7.自定义headers和cookies

     """自定义headers"""
     url = 'https://api.github.com/some/endpoint'
     headers = {'user-agent': 'my-app/0.0.1'}
 
     r = requests.get(url, headers=headers)
     # {'message': 'Not Found', 'documentation_url': 'https://developer.github.com/v3'}
     print(r.json())
 
     """自定义cookies"""
     url = 'http://httpbin.org/cookies'
     cookies = dict(cookies_are='working')
     cookies2 = {'cookies_are': 'working'}
 
     r = requests.get(url, cookies=cookies)
     # {'cookies': {'cookies_are': 'working'}}
     print(r.json())

8. SSL 证书验证

当发送请求如果报以上错误时,可以在请求方法里加多一个字段 verify=False ,就可以解决此问题;此操作是为了免去验证步骤

url = 'https://www.imooc.com'
res = requests.get(url, verify=False)

9. response响应

上面从4到6 都讲了如何发送请求requests,他的返回结果需要有对象接收,上代码:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import requests
print()

register_url = "https://v2.diancang.site/v2/verify"
# 添加请求头,需要就传
header = {
    "Content-Type": "application/json",
}
# json类型的参数
json = {
    "mobile": "15100424463",
    "code": "796305"
}
response = requests.post(url=register_url, json=json, headers=header)
print(response.json())

以下是一些方法

-- r.status_code         #响应状态码
-- r.content             #字节方式的响应体,会自动为你解码 gzip 和 deflate 压缩
-- r.headers             #以字典对象存储服务器响应头,但是这个字典比较特殊,字典键不区分大小写,若键不存在则返回None
-- r.json()             #Requests中内置的JSON解码器
-- r.url                # 获取url
-- r.encoding           # 编码格式
-- r.cookies            # 获取cookie
-- r.raw                #返回原始响应体
-- r.text               #字符串方式的响应体,会自动根据响应头部的字符编码进行解码
-- r.raise_for_status() #失败请求(非200响应)抛出异常

获取header

注意:headers里面的key是大小写不敏感的

>>> r.headers['Content-Type']  
'application/json'
>>> r.headers.get('content-type')
'application/json'

获取cookies

>>> r.cookies['example_cookie_name']   
'example_cookie_value'

老声长谈的状态码

状态代码有三位数字组成,第一个数字定义了响应的类别,共分五种类别:

1xx:指示信息--表示请求已接收,继续处理

2xx:成功--表示请求已被成功接收、理解、接受

3xx:重定向--要完成请求必须进行更进一步的操作

4xx:客户端错误--请求有语法错误或请求无法实现

5xx:服务器端错误--服务器未能实现合法的请求

常见状态码:

200 OK// 客户端请求成功

400 Bad Request// 客户端请求有语法错误,不能被服务器所理解

401 Unauthorized// 请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用

403 Forbidden// 服务器收到请求,但是拒绝提供服务

404 Not Found// 请求资源不存在,eg:输入了错误的URL

500 Internal Server Error// 服务器发生不可预期的错误

503 Server Unavailable// 服务器当前不能处理客户端的请求,一段时间后可能恢复正常

二、单接口测试实战

1.Content-Type的使用

一般我们在开发的过程中需要注意客户端发送请求(Request)时的Content-Type设置,特别是使用ajax的时候,如果设置得不准确,很有可能导致请求失败。比如:

  1. 在spring中,如果接口使用了@RequestBody,spring强大的自动解析功能,会将请求实体的内容自动转换为Bean,

  1. 请求的Content-Type必须设置为application/json,否正就会返回415错误。

  1. 注:415 错误是 Unsupported media type,即不支持的媒体类型。

  1. 如果是一个restful接口(json格式):

  1. 一般将Content-Type: application/json; charset=UTF-8;

  1. 如果是文件上传:

  1. 一般Content-Type :multipart/form-data

  1. 如果普通表单提交:

  1. 一般Content-Type :application/x-www-form-urlencoded

2. json数据类型详解

Json不是一种数据类型,只是一种特定格式内容的数据对象

注意:他不是字典

{
 "key1":"value1",
 "key2":"value2"
}

3.注意:

requests库实现的接口测试应用:

1. 导包

2. 接口请求的模拟

1. 确定协议是http/https

2. 确定请求需传递的参数

3. 如果需要对请求的内容做一定限制,可以在参数中进行设置

json 表示传入json格式的参数

timeout 表示本次接口的调用超时时长的定义

headers 表示本次接口传输时的headers当中自定义添加的内容

4. 在requests库中,进行请求模拟时,请标注清晰每一个参数分别是什么

三、接口自动化逻辑思维梳理

  1. 为什么要有接口自动化【一般哈,但凡有工作经验的不会 问这种弱智问题】但我还是要解释一下。

接口测试自动化,简单来讲就是功能测试用例脚本化,然后执行脚本,产生一份可视化测试报告。

不管什么样的测试方式,都是为了验证功能与发现 bug。

2. 那为什么要做接口测试自动化呢?【又是一个弱智问题】

一句话概括就是是为了节省人力成本。

具体来说,包括以下几点:

  1. 减轻自己工作量,把测试从枯燥的重复劳动的人工测试中解放出来;

2. 协助手工测试完成很难模拟或无法模拟的的工作;

3. 提高工作效率,比如测试环境的自动化编译、打包、部署、持续集成甚至持续交付等。

4. 协助定位问题,比如接口层发现问题了,可以通过添加的 traceID 定位到日志错误或错误代码行,尽早发现 Bug,自动通知测试人员。一旦发现问题,立即通知测试人员,快速高效。

3.明确接口测试自动化需要的功能

1、校验(断言)

测试断言是自动化测试中的测试通过条件,用于判断测试用例是否符合预期。

所以支持对返回值校验是一个必须的功能。

2、数据隔离

指具体的 请求接口、参数、校验数据做到与 代码相隔离,便于维护。

一旦需要调整接口用例、新增接口用例时可很快速的找到位置。

隔离的另一个好处就是可复用,框架可以推广给其他团队,使用者可以使用相同的代码,只需要根据要求填写各自用例即可测试起来。

3、数据传递
  1. 数据传递是另外一个更重要的需求:

  1. 接口测试时,首先我们会实现单接口解耦,后续按照业务场景组合多个接口。

  1. 而数据传递是则是组合多个接口的必要条件,它让接口用例之间可以做到向下传参。

举个例子:

我们通过设备信息查询接口查询到当前天猫精灵音箱的设备信息,该接口会返回一个 UUID,接下来我们要通过用户信息查询接口去查询当前设备绑定的用户信息,此时第二个接口的请求数据是需要从第一个接口用例中的返回中提取的。

4、功能函数

实际的业务场景测试会需要各种辅助功能的支持

比如随机生成时间戳,请求 ID,随机的手机号码或位置信息等等,此时我们就需要代码可以支持做到识别对应关键字时可以执行对应的功能函数进行填充。

5、可配置

目前测试环境包括 但不限于日常、预发一、预发二、线上等等。

因此用例不只能在一个环境上执行,需要同一份接口用例可以在日常、预发、线上等多个环境都可以执行。

所以框架需要做到可配置,便于切换,调用不同的配置文件可以在不同的环境执行。

6、日志包含执行的具体【具体可以看python中logging篇】

执行接口、请求方式、请求参数、返回值、校验接口、请求时间、耗时等关键信息,日志的好处一来是可以便于在新增用例有问题时快速定位出哪里填写有问题,二来是发现 bug 时方便向开发反馈提供数据,开发可以从触发时间以及参数等信息快速定位到问题所在。

7、可视化报告【具体可以看pytest中的Allure篇】

一个可视化的报告可以便于团队成员了解到每次自动化接口用例执行的成功数、失败数等数据。

8、可持续集成【具体可以看jenkins篇】

对于已经有测试用例并测试完成的接口,我们希望能够形成回归用例,在下一个版本迭代或上线之前,

通过已有用例进行一个回归测试,确保新上线的功能不影响已有功能。

因此,这就需要接口自动化测试是可持续集成的而不是一次性的。

9. 模块化思想

对于我们的接口自动化测试工程而言,需要能够创建小而独立的可以描述的模块、片断以及待测应用程

序的脚本。这些树状结构的小脚本组合起来,就能组成能用于特定的测试用例的脚本。

数据驱动思想

简而言之,就是测试脚本与测试数据分离。

让测试数据独立于测试脚本单独存在,解除脚本与数据之间的强耦合。

测试脚本不再负责管理测试数据,而测试数据在 数据驱动测试中会以文件或者数据库的形式存在。脚本每次执行会机械的从数据文件或者数据库中读入测试数据,根据测试数据的不同走进不同的测试路径。

关键字驱动思想【比较常用的】

这是一种更为高级的数据驱动测试,核心思想是将测试用例的每个步骤单独封装成一个函数,以这个函数名作为关键字,将函数名及传参写入文件中,每个步骤映射一行文件。

通过解析文件的每行内容,将内容拼成一个函数调用,调用封装好的步骤函数,就可以一步步执行测试案例。

在一个关键字驱动测试中,待测应用程序的功能和每个测试的执行步骤将被一起写到一个表中。

这一个思想通过很少的代码来产生大量的测试用例。

9. 哪些接口需要写自动化呢
1.优先级高的接口:

这类接口,出问题影响较大,因此在测试时,不管是手工还是自动化都要尽可能多覆盖,避免造成重大损失。

2. 重复测试次数较高的接口:

比如某些接口,参数特别多,并且近期因为代码修改频繁出现bug,手工测试特别耗时。

这种就可以用数据驱动的模式,将接口用例参数化,测试的时候关注重点参数进行多组数据测试即可高效完成测试

四、关于上面所说的巴拉巴拉一大堆【就知道你们想看这里】

以下是关于requests库的一个封装,上代码:

以下的代码是以关键字驱动进行封装的:所以说关键字驱动很重要.

首先要先考虑在接口自动化中什么最重要,当然是Content-type,因为他决定了参数类型


class RequestControl:

    # 兼容用户未填写headers或者header值为int【返回 headers】
    @classmethod
    def check_headers_str_null(cls, headers: Dict) -> Dict:
        """
        兼容用户未填写headers或者header值为int
        @return:
        """
        headers = ast.literal_eval(cache_regular(str(headers)))
        if headers is None:
            headers = {"headers": None}
        else:
            for key, value in headers.items():
                if not isinstance(value, str):  # isinstance 如果对象的类型与参数二的类型(classinfo)相同则返回 True,否则返回 False。
                    headers[key] = str(value)
        return headers

    """
    以下是直接进行封装request请求的方法 ,通过判断 requestType
    """
    # 判断请求POST requestType 类型为json 的直接进行request请求
    def request_type_for_json(self, headers: Dict, method: Text, **kwargs):
        """ 判断请求类型为json格式 """
        # 获取headers
        _headers = self.check_headers_str_null(headers)
        # 获取data
        _data = self.__yaml_case.data
        # 获取url
        _url = self.__yaml_case.url
        print(ast.literal_eval(cache_regular(str(_data))))

        # 执行 request需要的参数
        reponse = requests.request(
            method=method,
            url=cache_regular(str(_url)),
            json=ast.literal_eval(cache_regular(str(_data))),
            data={},
            headers=_headers,
            verify=False,
            params=None,
            **kwargs
        )

        return reponse

    # 判断请求GET requestType 为 params 的直接进行request请求
    def request_type_for_params(self, headers: Dict, method: Text, **kwargs):
        """处理 requestType 为 params """
        _data = self.__yaml_case.data
        url = self.__yaml_case.url
        if _data is not None:
            # url 拼接的方式传参
            params_data = "?"
            for key, value in _data.items():
                if value is None or value == '':
                    params_data += (key + "&")
                else:
                    params_data += (key + "=" + str(value) + "&")

            url = self.__yaml_case.url + params_data[:-1]

        _headers = self.check_headers_str_null(headers)
        res = requests.request(
            method=method,
            url=cache_regular(url),
            headers=_headers,
            verify=False,
            data={},
            params=None,
            **kwargs)
        return res

    # 处理 requestType 为 data 的直接进行request请求
    def request_type_for_data(self, headers: Dict, method: Text, **kwargs):
        """判断 requestType 为 data 类型"""
        data = self.__yaml_case.data
        _data, _headers = self.multipart_in_headers(

            ast.literal_eval(cache_regular(str(data))),
            headers
        )
        _url = self.__yaml_case.url
        res = requests.request(
            method=method,
            url=cache_regular(_url),
            data=_data,
            headers=_headers,
            verify=False,
            **kwargs)

        return res

    # 处理 requestType 为 file 类型 的直接进行request请求
    def request_type_for_file(self, method: Text, headers, **kwargs):
        """处理 requestType 为 file 类型"""
        multipart = self.upload_file()
        yaml_data = multipart[2]
        _headers = multipart[2].headers
        _headers = self.check_headers_str_null(_headers)
        res = requests.request(
            method=method,
            url=cache_regular(yaml_data.url),
            data=multipart[0],
            params=multipart[1],
            headers=ast.literal_eval(cache_regular(str(_headers))),
            verify=False,
            **kwargs
        )
        return res

    # 判断 requestType 为 None 的直接进行request请求
    def request_type_for_none(self, headers: Dict, method: Text, **kwargs) -> object:
        """判断 requestType 为 None"""
        _headers = self.check_headers_str_null(headers)
        _url = self.__yaml_case.url
        res = requests.request(
            method=method,
            url=cache_regular(_url),
            data=None,
            headers=_headers,
            verify=False,
            params=None,
            **kwargs
        )
        return res

    # 判断 requestType 为 export 导出类型
    def request_type_for_export(self, headers: Dict, method: Text, **kwargs):
        """判断 requestType 为 export 导出类型"""
        _headers = self.check_headers_str_null(headers)
        _data = self.__yaml_case.data
        _url = self.__yaml_case.url
        res = requests.request(
            method=method,
            url=cache_regular(_url),
            json=ast.literal_eval(cache_regular(str(_data))),
            headers=_headers,
            verify=False,
            stream=False,
            data={},
            **kwargs)
        filepath = os.path.join(ensure_path_sep("\\Files\\"), self.get_export_api_filename(res))  # 拼接路径
        if res.status_code == 200:
            if res.text:  # 判断文件内容是否为空
                with open(filepath, 'wb') as file:
                    # iter_content循环读取信息写入,chunk_size设置文件大小
                    for chunk in res.iter_content(chunk_size=1):
                        file.write(chunk)
            else:
                print("文件为空")
        return res


    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值