IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发测试 -> 软件测试 接口自动化测试 pytest框架封装 requests库 封装统一请求和多个基础路径处理 接口关联封装 测试用例写在yaml文件中 数据热加载(动态参数) 断言 -> 正文阅读

[开发测试]软件测试 接口自动化测试 pytest框架封装 requests库 封装统一请求和多个基础路径处理 接口关联封装 测试用例写在yaml文件中 数据热加载(动态参数) 断言

文章目录

1 requests库

requests用于发送http请求的第三方库。

1.1 requests库常用的方法

requests库常用的方法有请求方式、post请求的格式、request()方法参数和rquests()执行之后返回respones对象信息。

1.1.1 请求方式

方法及其参数功能
get(url, params=None, **kwargs)发送get请求
post(url, data=None, **kwargs)发送post请求
delete(url, **kwargs)发送delete请求
request发送任何请求(重要功能
session()发送任意请求(核心功能

1.1.2 post请求的格式

参考Postman,在Postman中row方式下还有5种格式,如下图所示:
在这里插入图片描述

post请求正文格式,如下表所示:

格式描述
multipart/form-data键值对或文件上传
x-www-form-urlencoded表单方式传参,如:key1=value1&key2=value2
row中的application/jsonjson格式传参
row中的application/plainplain格式传参
row中的application/javascriptjavascript格式传参
row中的application/htmlhtml格式传参
row中的application/xmlxml格式传参
binary二进制流的数据

1.1.3 request()方法参数

requests库中的request()方法参数,如下表所示:

参数描述
method请求方式
url请求路径
paramsget方式传参
datapost,put,patch传参
headers请求头
cookies请求的cookie信息
files文件上传
jsonpost传参
auth鉴权
timeout超时
allow_redirects重定向
proxies代理
hooks钩子方法
stream文件下载
verify是否需要验证证书
certCA证书

1.1.4 rquests()执行之后返回respones对象信息

import requests
class TestRequests:
    def test_get(self):
        url = "https://api.weixin.qq.com/cgi-bin/token"
        params ={
            "grant_type":"client_credential",
            "appid":"wx2c4830ee4aa8ffa9",
            "secret": "f889ab43e8fafc488bd905b14f464d08"
        }
        # rquests()执行之后返回respones对象信息
        respones = requests.get(url=url,params=params)
        # 响应信息以字符串格式输出
        print(respones.text)
        # 响应信息以bytes类型格式输出
        print(respones.content)
        # 响应信息以json格式输出
        print(respones.json())
        # 响应的状态码
        print(respones.status_code)
        # 响应的状态信息
        print(respones.reason)
        # 响应的cookie信息
        print(respones.cookies)
        # 响应头信息
        print(respones.headers)
        # 请求头信息
        print(respones.request.headers)

1.2 实战

以微信公众平台为例子。
点击查看详细信息

1.2.1 发送get请求

例子:微信公众号查询已创建的标签。
代码如下:

import requests
class TestRequests:
    access_token=""
    def test_get(self):
        url = "https://api.weixin.qq.com/cgi-bin/token"
        params ={
            "grant_type":"client_credential",
            "appid":"xxxxxxx",
            "secret": "xxxxxxxxxxx"
        }
        # rquests()执行之后返回respones对象信息
        respones = requests.get(url=url,params=params)
        return_data = respones.json()
        print(return_data)
   		# 返回的token值设置为变量
        TestRequests.access_token = return_data['access_token']
    def test_select(self):
        url = "https://api.weixin.qq.com/cgi-bin/tags/get"
        params ={
            "access_token":TestRequests.access_token
        }
        respones = requests.get(url=url, params=params)
        return_data = respones.json()
        print(return_data)

分析上述代码:
(1)test_get()方法负责获取token值。添加access_token为类变量,将test_get()得到的token值传给access_token,供下列操作使用。
(2)test_select()方法负责查看操作,使用requests库中的get()方法,get()方法需要url参数和params参数,请求之后响应的数据以json(在Python一般称为字典)格式输出。

1.2.2 发送post请求,data传参

data传参针对数据为键值对的字典。
例子:网易新闻接口
代码如下:

import requests
class TestRequests:
    def test_post_data(self):
        url = "https://api.apiopen.top/getWangYiNews"
        data = {
            "page":"1",
            "count":"5"
        }
        res = requests.post(url=url,data=data)
        # 查看请求头
        print(res.request.headers)
        # 查看响应信息
        print(res.text)

分析上述代码:
(1)requests库中中的post()方法需要两个参数url和data。
(2)data传参请求头默认是'Content-Type': 'application/x-www-form-urlencoded'形式,如下图所示:
在这里插入图片描述

1.2.3 发送post请求,json传参

json传参针对数据为嵌套的字典。
例子:微信公众平台修改标签信息。
代码如下:

import requests
class TestRequests:
    access_token=""
    # 修改标签
    def test_post_json(self):
        # 字符串拼接token值
        url = "https://api.weixin.qq.com/cgi-bin/tags/update?access_token="+TestRequests.access_token
        # 数据形式是嵌套的字典
        data ={
            "tag":{
                "id":103,"name":"北京"+str(random.randint(1000,9999)) #采用random包,使用随机数
                    }
               }
        res = requests.post(url=url,json=data)
        print(res.request.headers)
        print(res.json())

分析上述代码:
(1)获取token值由test_get()方法负责。
(2)json传参请求头默认是'Content-Type': 'application/json'形式,如下图所示:
在这里插入图片描述
此外,可以使用json.dumps()将字典形式转换为字符串,post()方法参数则改为data,如下图所示:
在这里插入图片描述
注:
json.dumps(data),将Python对象(这里是字符串)转换为字符串。
json.loads(data),将字符串转换WiePython对象。

1.2.4 发送post请求,file传文件

例子:微信公众平台上传文件
代码如下:

 def test_post_file_upload(self):
        url = "https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token="+TestRequests.access_token
        data ={
            "media":open(r".\pic\123.png","rb")
        }
        res = requests.post(url=url,files=data)
        print(res.request.headers)
        print(res.json())

分析上述代码:
(1)文件的相对路径。在all.py文件作文运行入口,而all.py位于项目根目录的下一级。打开文件时,使用相对路径。pic文件夹位于项目根目录的下一级,pic文件夹下存放123.png。r使得转义字符\无效。
(2)post()方法中的files参数。使用files参数进行文件上传。

运行结果有url地址,复制到浏览器,即可查看图片,如下图所示:
在这里插入图片描述

file参数请求头默认是'multipart/form-data; boundary=76b7c36bf9c845dcaf20b1970879ea5b',如下图所示:
在这里插入图片描述

1.3 需要带请求头的接口

例子:phpwind网站的post请求必须请求头。
(1)找到token值
参考API使用说明文档,代码如下:

class TestRequests:
    csrf_token = ""
    def test_phpwind_start(self):
        url = "http://47.107.116.139/phpwind/"
        res = requests.get(url=url)
        return_data = res.text
        print(return_data)

在结果中找到token值,如下图所示:
在这里插入图片描述
(2)利用正则表达式获取token值,将token值传入类变量csrf_token,如下所示:

class TestRequests:
    csrf_token = ""
    def test_phpwind_start(self):
        url = "http://47.107.116.139/phpwind/"
        res = requests.get(url=url)
        return_data = res.text
        # 使用正则表达式提取token值
        obj = re.search('name="csrf_token" value="(.*?)"',return_data)
        TestRequests.csrf_token = obj.group(1)
        # 查看token值
        print(TestRequests.csrf_token)

(3)添加登录账号测试用例
代码如下:

class TestRequests:
    def test_login(self):
        url = "http://47.107.116.139/phpwind/index.php?m=u&c=login&a=dorun"
        data = {
            "username":"xxxxxx",
            "password": "xxxx",
            "csrf_token": TestRequests.csrf_token,
            "backurl": "http://47.107.116.139/phpwind/",
            "invite": ""
        }
        res = requests.post(url=url,data=data)
        print(res.request.headers)
        print(res.json())

注:账号和密码需要提前注册好,注册地址http://47.107.116.139/phpwind/
此外,该接口还需要请求头。
(4)添加请求头
在原来的登录账号接口中添加请求头,并传入post()方法中,如下图所示:
在这里插入图片描述
到此,运行还是失败。
原因在于没有做cookie关联。需要带请求头的接口,一般都需要做cookie关联。

1.4 cookie关联

(1)在test_phpwind_start()测试用例中获取token的同时,获取cookie值,如下图所示:
在这里插入图片描述

(2)在登录账号测试用例,test_login,post()请求中添加cookie,如下图所示:
在这里插入图片描述
(3)运行结果成功,如下图所示:
在这里插入图片描述
但是,一般情况不适用cookie,而是适用session对象。

1.5 session对象关联

使用session发送请求,会自动进行cookie关联。
(1)添加session回话类变量,如下所示:

session = requests.session()

(2)调用get、post请求方法使用session对象调用request()方法,如下所示:

res = TestRequests.session.request("get",url=url)

res = TestRequests.session.request("post",url=url,data=data,headers=headers)

综上代码如下:

import re
import requests
class TestRequests:
	csrf_token = ""
    # 添加session回话类变量
    session = requests.session() #!!!!
    def test_phpwind_start(self):
        url = "http://47.107.116.139/phpwind/"
        res = TestRequests.session.request("get",url=url)
        return_data = res.text
        # print(return_data)
        # 使用正则表达式提起token值
        obj = re.search('name="csrf_token" value="(.*?)"',return_data)
        TestRequests.csrf_token = obj.group(1)
        print(TestRequests.csrf_token)
    def test_login(self):
        url = "http://47.107.116.139/phpwind/index.php?m=u&c=login&a=dorun"
        data = {
            "username":"Conlin_Top",
            "password": "123456",
            "csrf_token": TestRequests.csrf_token,
            "backurl": "http://47.107.116.139/phpwind/",
            "invite": ""
        }
        headers ={
            "Accept":"application/json,text/javascript, /; q=0.01",
            "X-Requested-With":"XMLHttpRequest"
        }
        res = TestRequests.session.request("post",url=url,data=data,headers=headers) 
        print(res.request.headers)
        print(res.json())

2 封装统一请求和多个基础路径处理

请求可以写在一个方法send_request()中,请求方式(get或post),url地址,传入数据(data,json,files)以参数形式传入即可。

2.1 request_util.py

request_util.py用于请求方式。
放在项目根目录下的common文件夹下,如下图所示:
在这里插入图片描述

import requests


class RequestsUtil:
    # 创建session对象
    session = requests.session()
    # 发送请求,**kwargs表示可变长度的字典
    def send_request(self,method,url,**kwargs):
        res = RequestsUtil.session.request(method=method,url=url,**kwargs)
        return res

2.2 对测试用例分类

根据url不同进行分类。
test_request01.py存放https://api.weixin.qq.com的测试用例。
test_request02.py存放https://api.apiopen.top的测试用例。
test_request03.py存放http://47.107.116.139的测试用例。
项目结构,如下图所示:
在这里插入图片描述

test_requests01部分代码如下所示:

from common.requests_util import RequestsUtil
class TestRequests01:
    access_token=""
    # 获取token值
    def test_get(self):
        url = "https://api.weixin.qq.com/cgi-bin/token"
        params ={
            "grant_type":"client_credential",
            "appid":"wx2c4830ee4aa8ffa9",
            "secret": "f889ab43e8fafc488bd905b14f464d08"
        }
        # 调用send_request方法,发送请求
        res = RequestsUtil().send_request(method="get",url=url,params=params)

        return_data = res.json()
        print(return_data)
        # 将token值保存在access_token变量
        TestRequests01.access_token = return_data['access_token']

分析上述代码:
(1)在test_request01.py调用request_util.py中的send_request方法,需要导包.
(2)调用send_request方法,发送get请求。
(2)将token值设置为类变量。

发现问题:url地址还是写在测试用例中,后期不方便修改。

2.3 config.yml

config.yml存放url地址。
config.yml文件内容如下图所示:
在这里插入图片描述

除了要有存放url地址的文件,还要有读取url的功能,可以将读取url的功能放在common中。

2.4 yaml_util.py

读取url的功能是yaml_util.py,代码如下所示:

import os
import yaml

# 获取项目的根目录
def get_object_path():
    return os.getcwd().split('common')[0]

# 读取config.yml文件
def read_config_file(base, base_url):
    with open(get_object_path() + "/config.yml", encoding="utf-8") as f:
        value = yaml.load(stream=f, Loader=yaml.FullLoader)
        return value[base][base_url]

# 测试输出
if __name__ == '__main__':
    print(read_yaml('base','base_gzh_url'))
    print(read_yaml('base', 'base_wyn_url'))
    print(read_yaml('base', 'base_php_url'))

2.5 完善requests_util.py中的url

代码如下图所示:
在这里插入图片描述
分析上述代码:
(1)请求必须要url地址,则放在__init__方法中。调用读取config.yml文件的方法为read_config_file(base,base_url)
(2)拼接完整的url。

2.6 测试用例调用RequestsUtil类中的方法

代码如下:
在这里插入图片描述
分析上述代码:
调用RequestsUtil类中的__init__方法。

2.7 完善requests_util.py中的method

为了防止出现“POST”或“Post”导致的报错,在这里处理一下method,如下图所示:
在这里插入图片描述

3 接口关联封装

接口关联封装,解析{{access_token}}

3.1 新建extract.yml

在跟目录新建extract.yml,存放token值。

3.2 读取、写入和清空extract.yml文件内容

在common包下的yam_util.py文件里,创建读取、写入和清空extract.yml的方法,如下所示:

# 读取extract.yml文件
def read_extract_yml(node):
    with open(get_object_path()+"/extract.yml",encoding="utf-8") as f:
        value =  yaml.load(stream=f, Loader=yaml.FullLoader)
        return value[node]
# 将token值写入extract.yml文件
def write_extract_file(data):
    with open(get_object_path()+"/extract.yml",encoding="utf-8",mode="a") as f:
        yaml.dump(data, stream=f, allow_unicode=True)
# 清空extract.yml文件
def clear_extract_file():
    with open(get_object_path()+"/extract.yml",encoding="utf-8",mode="w") as f:
        f.truncate()

3.3 调用写入extract.yml文件的方法

写入extract.yml文件的方法,需要屏蔽使用token值的测试用例,避免出错。
在testcases01.py中,调用写入extract.yml文件的方法,如下图所示:
在这里插入图片描述

在testcases03.py中,调用写入extract.yml文件的方法,如下图所示:
在这里插入图片描述
查看结果成功,如下图所示:
在这里插入图片描述

3.4 调用读取extract.yml文件的方法

在testcases01.py中,调用读取extract.yml文件的方法,如下图所示:
在这里插入图片描述
在这里插入图片描述
运行all.py文件,结果成功,如下图所示:
在这里插入图片描述
多次运行all.py文件,有多个token值,如下图所示:
在这里插入图片描述

原因:读取token值,extract.yml只会读取最新的token值。
从长远来看,这不是好办法,还得之前在yaml_util.py有清空extract.yml的办法吗?

3.5 新建conftest.py

在conftest.py每次运行all.py之前自动执行clear_extract_file()方法,到达每次运行all.py之前清空extract.yml的目的。
conftest.py内容,如下图所示:
在这里插入图片描述

封装的意义在于不用写代码,在yaml文件中写测试用例。
存在的问题:
在测试用例中还是有很多数据没有集成在yaml文件中。

3.6 提取token值写入url地址中

目标:从extract.yml中提取token值写入url地址中
在requests_util.py做测试。
在这里插入图片描述
分析上述代码:
(1)知识点。字符串切片
(2)加入for循环是为了处理出现多个{{的情况
(3)核心思想,提取{{access_token}}字符串中的access_token字符串,通过与extract.yml文件中的access_token对应上,找到token值,再把token值放入url地址中。

拓展

问题:但是token值有可能放在url地址中,也有可能放在params中,也有可能放在请求头中,这时怎么办?
要求:需要在发送请求时,url地址跟上token值,例如:

https://api.weixin.qq.com/cgi-bin/tags/update?access_token={{access_token}}

思路:解析{{access_token}}中的access_token,通过extract.yml中的access_token,找到对应的token值。
办法:处理请求的方法放在request_util.py中,统一替换token值的办法,这样token值可以放在url地址、params和请求头中。
技术难题:token值在url是string类型,在params中是字典或字典嵌套列表类型,在请求头是字典类型,所以首先,将token值统一为字符型,然后将读取token值,还原原来的数据类型,最后返回token值。
代码如下:

# 统一替换的方法
    def replace_value(self,data):
    # 不管什么类型统一转化成字符串格式
        if data and isinstance(data,dict): # 如果data不为空并且data的类型是一个字典
            str = json.dump(data)
        else:
            str = data
        # 替换为str
        for i in range(1,str.count("{{")+1):
            if "{{" in str and "}}" in str:
                start_index = str.index("{{")
                end_index = str.index("}}")
                old_value = str[start_index:end_index+2]
                new_value = read_extract_yml(old_value[2:-2])
                str = str.replace(old_value,new_value)
                print(str)
        # 还原数据类型
        if data and isinstance(data,dict):
            data = json.load(str)
        else:
            data = str
        # 返回
        return data

4 测试用例写在yaml文件中

分析yaml文件,接口关联中解析extract关键字。
可以参考httprunner的yaml文件。注:yaml和yml是一个意思,不做区分。
以微信公众号为例子。

4.1 初步写get_token.yml

参考httprunner的yaml文件,name,base_url,request和validate作为一级关键字,get_token.yml内容,如下图所示:
在这里插入图片描述

4.2 写读取yml文件中数据的方法

在yaml_util.py文件写读取yaml测试用例文件的方法,如下所示:

# 读取yaml测试用例数据
def read_testcase_file(yaml_path):
    with open(get_object_path()+yaml_path,encoding="utf-8") as f:
        value = yaml.load(stream=f,Loader=yaml.FullLoader)
        return value

4.3 数据驱动yml文件中的数据

使用pytest.mark.parametrize()作为数据驱动。
在测试用例上使用装饰器,装饰器调用读取yaml测试用例数据。
在test_request01.py写,如下代码:

class TestRequests01:
    @pytest.mark.parametrize("caseinfo",read_testcase_file("/testcases/get_token.yml"))
    def test_get(self,caseinfo):
        print(caseinfo)

4.4 接口自动化测试框架规则

记录规则放在txt文件中,在根目录的下一级新建接口自动化测试框架规则,如下图所示:
在这里插入图片描述

4.5 一级关键字规则

必须有的四个一级关键字:name,base_url,request,validate
这四个关键字涉及请求,故在request_util.py写规范yaml文件的方法。
代码实现,如下所示:

class RequestsUtil:
    # 规范yaml文件测试用例文件
    def analysis_yaml(self,caseinfo):
        # print(caseinfo)
        # 1.必须有的四个一级关键字:name,base_url,request,validate
        caseinfo_keys = dict(caseinfo).keys()
        if "name" in caseinfo_keys and "base_url" in caseinfo_keys and "request" in caseinfo_keys and "validate" in caseinfo_keys:
            pass
        else:
            print("四个一级关键字:name,base_url,request,validate必须同时出现!")

分析上述代码:
dict()方法将数据转换为字典类型,方便通过键取值。

在测试用例使用analysis_yaml()方法,测试analysis_yaml()方法是否书写正确,在使用analysis_yaml()需要实例化RequestsUtil('base', 'base_gzh_url'),代码如下所示:

class TestRequests01:

    @pytest.mark.parametrize("caseinfo", read_testcase_file("/testcases/get_token.yml"))
    def test_get(self,caseinfo):
        # 实例化,才能调用类中的方法(这里是analysis_yaml()方法)
        RequestsUtil('base', 'base_gzh_url').analysis_yaml(caseinfo)
        print(caseinfo)

4.6 二级关键字规则

request一级关键字下必须包括两个二级关键字:method,url。
必须通过了一级关键字进一步筛选二级关键字,故二级关键字在一级关键字下写if分支,代码如下图所示:
在这里插入图片描述

4.7 处理files和headers

进行post请求时,有时候需要files传值和headers传值,这里找到这两个值,直接删除,不处理,代码如下所示:

class RequestsUtil:
    def analysis_yaml(self,caseinfo):
		# 请求头约束
		headers = None
		if jsonpath.jsonpath(caseinfo,"$..headers"):
			headers = caseinfo["request"]["headers"]
			del caseinfo["headers"]
		# 文件上传约束
		files = None
		if jsonpath.jsonpath(caseinfo,"$..files"):
			files = caseinfo["request"]["files"]
			del caseinfo["files"]

分析上述代码:
(1)caseinfo就是get_token.yml文件的内容(request)。
(2)jsonpath找到headers或files的内容,del删除caseinfo中的files键值对。

4.8 发送“获取token值”请求

发送请求只需要appid值和secret值。
send_request()方法需要method,url,headers和files参数,则将他们全部清空,剩下的数据(appid值和secret值)以**kwargs形式传入,得出的access_token字符串和token值需要写入到extract.yml文件。

(1)在4.7的清空办法下,紧接着发送请求,如下所示:

	res = self.send_request(method=method,
							url=url,
                           	headers=headers,
                            files=files,
                            **caseinfo["request"])
   	print(res.text)
	return_data = res.json()
	return_text = res.text
	print(return_text)

token值已经返回成功了,问题来了,如何提取呢?
使用json提取器或正则表达式提起。
(2)使用正则表达式提取
因为token值已经返回成功了,所以可以在get_token.yml写正则表达式提取,如下图所示:
在这里插入图片描述

(3)使用json提取
因为token值已经返回成功了,所以可以在get_token.yml写json提取,如下图所示:
在这里插入图片描述

综上,代码如下:

# 提取接口关联的变量,要支持正则表达式,又要支持json提取
	if "extract" in caseinfo_keys:
		for key,value in dict(caseinfo["extract"]).items():
		# 正则提取token值
			if "(.*?)" in value or "(.+?)" in value:
				zz_value = re.search(value,return_text)
				extract_data = {key: zz_value.group(1)}
				write_extract_file(extract_data)

			else: #json提取
				extract_data = {key:return_data[key]}
				write_extract_file(extract_data)
		print(key,return_data[key])

4.9 发送“查询标签”请求,“修改标签”请求,“文件上传”请求

发送“查询标签”和“修改标签”请求区别主要在yaml文件上url,传入格式(params,json,data)和传入的参数。

4.9.1 发送“查询标签”请求

(1)新建select_flag.yml
在testcases文件夹下新建select_flag.yml
目的是为了存放请求地址和请求的参数,内容如下图所示:
在这里插入图片描述

(2)编写测试用例
在test_requests01.py文件下的TestRequests01类编写测试用例,如下所示:

@pytest.mark.parametrize("caseinfo",read_testcase_file("/testcases/select_flag.yml"))
    def test_select(self,caseinfo):
        res = RequestsUtil('base', 'base_gzh_url').analysis_yaml(caseinfo)

分析上述代码:
1)使用数据驱动的装饰器修饰测试用例。
2)实例化request_util.py中的RequestsUtil类,调用analysis_yaml()方法。

(3)运行结果成功如下所示:
在这里插入图片描述

4.9.1 发送“修改标签”请求

(1)新建edit_flag.yml
在testcases文件夹下新建select_flag.yml
目的是为了存放请求地址和请求的参数,内容如下图所示:
在这里插入图片描述

(2)编写测试用例
在test_requests01.py文件下的TestRequests01类编写测试用例,如下所示:

    @pytest.mark.parametrize("caseinfo", read_testcase_file("/testcases/edit_flag.yml"))
    def test_edit(self, caseinfo):
        res = RequestsUtil('base', 'base_gzh_url').analysis_yaml(caseinfo)

(3)运行结果成功如下图所示:
在这里插入图片描述
存在的问题:
在name之后的属性值上没有随机数,无法多次执行,即动态参数没有实现。
办法:使用debug_talk.py热加载的方式

4.9.3 发送“文件上传”请求

(1)新建file_upload.yml
修改url,参数,内容如下图所示:
在这里插入图片描述

在yaml文件无法使用open函数,所以要去requests_util.py的analysis_yaml()进行处理文件流。

(2)修改analysis_yaml()方法
在读取yaml中的media: "./pic/123.png"这里是字典,需要for循环遍历字典的内容,在requests_util.py的analysis_yaml()方法,添加代码如下所示:
在这里插入图片描述

(3)编写测试用例
在test_requests01.py文件下的TestRequests01类编写测试用例,如下所示:

    # 文件上传
    @pytest.mark.parametrize("caseinfo", read_testcase_file("/testcases/file_upload.yml"))
    def test_up_load(self, caseinfo):
        res = RequestsUtil('base', 'base_gzh_url').analysis_yaml(caseinfo)

(4)运行结果成功,如下图所示:
在这里插入图片描述
针对4.9.1存在的问题:
在name之后的属性值上没有随机数,无法多次执行,即动态参数没有实现。
办法:使用debug_talk.py热加载的方式

5 数据热加载(动态参数)

5.1 新建debug_talk.py

目的:参数动态化
代码如下图所示:
在这里插入图片描述

5.2 edit_flag.yml

目的:调用参数动态化
在这里插入图片描述
yaml文件无法调用Python中的函数,需要解析,即写一个方法解析${get_random_number(10000,99999)}

5.3 replace_load()

目的:解析${get_random_number(10000,99999)}
先提取,再代替,实现解析。

(1)提取${get_random_number(10000,99999)}
在requests_util.py中写replace_load()方法。然后,提取${get_random_number(10000,99999)},代码如下所示:

    def replace_load(self,data):
        if data and isinstance(data,dict): # 如果data不为空并且data的类型是一个字典
           str_data = json.dumps(data)
        else:
            str_data = data
        # 替换为str_data
        for i in range(1,str_data.count("${") + 1):
            if "${" in str_data and "}" in str_data:
                start_index = str_data.index("${")
                end_index = str_data.index("}",start_index)
                old_value = str_data[start_index:end_index + 1]
                print(old_value) #${get_random_number(10000,99999)}

最后在main方法做测试,代码如下所示:

if __name__ == '__main__':
    data = {"tag": {"id":108,"name": "王菲${get_random_number(10000,99999)}"}}
    RequestsUtil('base', 'base_gzh_url').replace_load(data)

(2)反射
通过一个函数的字符串直接去调用这个方法。

# 获取函数名
func_name = old_value[2:old_value.index('(')]
print(func_name)
# 获取参数值
args_value =old_value[old_value.index('(')+1:old_value.index(')')]
print(args_value.split(','))# 以,分割为两个参数 ['10000', '99999']
# 反射(通过一个函数的字符串直接去调用这个方法)
new_value = getattr(DebugTalk(),func_name)(*args_value.split(',')) # *去掉[] '10000', '99999'
print(new_value)
str_data = str_data.replace(old_value,str(new_value))

(3)添加返回值

if data and isinstance(data,dict):
	data = json.loads(str_data)
else:
	data = str_data
	return data

综上,replace_load()代码如下所示:

class RequestsUtil:
    def replace_load(self,data):
        if data and isinstance(data,dict): # 如果data不为空并且data的类型是一个字典
           str_data = json.dumps(data)
        else:
            str_data = data
        # 替换为str_data
        for i in range(1,str_data.count("${") + 1):
            if "${" in str_data and "}" in str_data:
                start_index = str_data.index("${")
                end_index = str_data.index("}",start_index)
                old_value = str_data[start_index:end_index + 1]
                print(old_value) #${get_random_number(10000,99999)}
                # 获取函数名
                func_name = old_value[2:old_value.index('(')]
                print(func_name)
                # 获取参数值
                args_value = old_value[old_value.index('(')+1:old_value.index(')')]
                print(args_value.split(','))# 以,分割为两个参数 ['10000', '99999']
                # 反射(通过一个函数的字符串直接去调用这个方法)
                new_value = getattr(DebugTalk(),func_name)(*args_value.split(',')) # *去掉[] '10000', '99999'
                print(new_value)
                # new_value = read_extract_yml(old_value[2:-2])
                str_data = str_data.replace(old_value,str(new_value))
        if data and isinstance(data,dict):
            data = json.loads(str_data)
        else:
            data = str_data
        return data

5.4 access_token使用热加载方式读取

5.4.1 在debug_talk.py中添加get_extract_data方法

如下所示:
在这里插入图片描述

5.4.2 修改select_flag.yml中的参数值

将{{access_token}}改为"${get_extract_data(access_token)}"
在这里插入图片描述
同时也可以将file_upload.yml中的url地址改为,如下图所示:
在这里插入图片描述

5.4.3 在send_request调用replace_load()方法

如下图所示:
在这里插入图片描述
编写4条测试用例,代码如下:

class TestRequests01:
    # 获得token值
    @pytest.mark.parametrize("caseinfo", read_testcase_file("/testcases/get_token.yml"))
    def test_get_token(self,caseinfo):
        # 实例化,才能调用类中的方法(这里是analysis_yaml()方法)
        res = RequestsUtil('base', 'base_gzh_url').analysis_yaml(caseinfo)
    # 查询标签
    @pytest.mark.parametrize("caseinfo", read_testcase_file("/testcases/select_flag.yml"))
    def test_select(self,caseinfo):
        res = RequestsUtil('base', 'base_gzh_url').analysis_yaml(caseinfo)

    # 修改标签
    @pytest.mark.parametrize("caseinfo", read_testcase_file("/testcases/edit_flag.yml"))
    def test_edit(self, caseinfo):
        res = RequestsUtil('base', 'base_gzh_url').analysis_yaml(caseinfo)

    # 文件上传
    @pytest.mark.parametrize("caseinfo", read_testcase_file("/testcases/file_upload.yml"))
    def test_up_load(self, caseinfo):
        res = RequestsUtil('base', 'base_gzh_url').analysis_yaml(caseinfo)

6 断言

断言判断实际结果和预期结果是否一致。
在发送请求之后就会有实际结果,所以在发送请求之后调用断言方法。

现在只用“获得token值”作为例子。

6.1 写validate_result()方法

目的:判断实际结果和预期结果是否一致。
在reqquest_util.py模块下写断言方法,代码如下所示:

    def validate_result(self,planned_result,actual_result,status_code):
        '''

        :param planned_result:预期结果
        :param actual_result: 实际结果
        :param status_code: 实际返回的状态码
        :return:
        '''
        # print("预期结果:",planned_result)
        # print("实际结果:",actual_result)
        # 断言是否成功的标记,0成功,其他是失败
        flag = 0
        #解析断言
        if planned_result and isinstance(planned_result,list):
            for i in planned_result:
                # print(i)
                for key, value in dict(i).items():
                    # print(key,value)
# 预期结果: [{'equals': {'status_code': 200}}, {'equals': {'expires_in': 7200}},{'contains': 'access_token'}]

                    if key=="equals":
                        for assert_key,assert_value in dict(value).items():
                            # print(assert_key,assert_value)
                            if assert_key =="status_code":
                                if status_code != assert_value:
                                    flag = flag + 1
                                    print("断言失败:"+assert_key+"不等于"+str(status_code))
                            else:
                                key_list = jsonpath.jsonpath(actual_result,'$..%s'%assert_key)
                                if key_list:
                                    if assert_value not in key_list:
                                        flag = flag + 1
                                        print("断言失败:"+assert_key+"不等于"+str(assert_value))
                                else:
                                    flag = flag + 1
                                    print("断言失败:返回结果不存在:"+assert_key)
                    elif key == "contains":
                        if value not in json.dumps(actual_result):
                            flag = flag + 1
                            print("断言失败,返回结果不包含字符串"+value)
                    else:
                        print("该框架不支持此断言")
        assert flag == 0

6.2 在发送请求之后调用validate_result()

目的:在发送请求之后就有了实际结果了,实际结果就可以和预期结果比较了。

6.3 修改get_token.yml中的断言格式

如下图所示:
在这里插入图片描述

6.4 equals类型断言

# 预期结果: [{'equals': {'status_code': 200}}, 
		#  {'equals': {'expires_in': 7200}},
		#  {'contains': 'access_token'}]
	if key=="equals":
		for assert_key,assert_value in dict(value).items():
		print(assert_key,assert_value)
		if assert_key =="status_code":
			if status_code != assert_value:
				flag = flag + 1
				print("断言失败:"+assert_key+"不等于"+str(status_code))
		else:
			key_list = jsonpath.jsonpath(actual_result,'$..%s'%assert_key)
				if key_list:
					if assert_value not in key_list:
						flag = flag + 1
						print("断言失败:"+assert_key+"不等于"+str(assert_value))
	else:
	flag = flag + 1
	print("断言失败:返回结果不存在:"+assert_key)

6.5 contains类型断言

	elif key == "contains":
		if value not in json.dumps(actual_result):
			flag = flag + 1
			print("断言失败,返回结果不包含字符串"+value)
	else:
		print("该框架不支持此断言")

存在问题:
断言失败,但是用例通过了。
解决办法:
在def validate_result()方法,添加assert flag = 0
如下代码所示:

def validate_result(self,planned_result,actual_result,status_code):
	if planned_result and isinstance(planned_result,list):
		......
	assert flag == 0

衍生了问题:
当一条用例不通过时,即使其他3条用例是正确,都会不通过。

原因:
extract.yml文件为空,即断言失败后,token值无法写入extract.yml文件中。
解决办法:
在提取token值之后才做断言。
代码如下所示:
在这里插入图片描述

运行all.py,如下图所示:
在这里插入图片描述
达到预期结果!

综上,断言方法写在requests.py下RequestsUtil类中,如下代码所示,

class RequestsUtil:
 def validate_result(self,planned_result,actual_result,status_code):
        '''

        :param planned_result:预期结果
        :param actual_result: 实际结果
        :param status_code: 实际返回的状态码
        :return:
        '''
        # print("预期结果:",planned_result)
        # print("实际结果:",actual_result)
        # 断言是否成功的标记,0成功,其他是失败
        flag = 0
        #解析断言
        if planned_result and isinstance(planned_result,list):
            for i in planned_result:
                # print(i)
                for key, value in dict(i).items():
                    # print(key,value)
# 预期结果: [{'equals': {'status_code': 200}}, {'equals': {'expires_in': 7200}},{'contains': 'access_token'}]

                    if key=="equals":
                        for assert_key,assert_value in dict(value).items():
                            print(assert_key,assert_value)
                            if assert_key =="status_code":
                                if status_code != assert_value:
                                    flag = flag + 1
                                    print("断言失败:"+assert_key+"不等于"+str(status_code))
                            else:
                                key_list = jsonpath.jsonpath(actual_result,'$..%s'%assert_key)
                                if key_list:
                                    if assert_value not in key_list:
                                        flag = flag + 1
                                        print("断言失败:"+assert_key+"不等于"+str(assert_value))
                                else:
                                    flag = flag + 1
                                    print("断言失败:返回结果不存在:"+assert_key)
                    elif key == "contains":
                        if value not in json.dumps(actual_result):
                            flag = flag + 1
                            print("断言失败,返回结果不包含字符串"+value)
                    else:
                        print("该框架不支持此断言")
        assert flag == 0

小结

1 使用session发送请求,会自动进行cookie关联。
2 响应信息以json格式输出print(respones.json()),响应的状态码print(respones.status_code),请求头信息print(respones.request.headers)。
3 请求可以写在一个方法send_request()中,代码如下:

import requests
class RequestsUtil:
    # 创建session对象
    session = requests.session()
    # 发送请求,**kwargs表示可变长度的字典
    def send_request(self,method,url,**kwargs):
        res = RequestsUtil.session.request(method=method,url=url,**kwargs)
        return res

分析上述代码:
请求方式(get或post),url地址,传入数据(data,json,files)以参数形式传入即可。
4 接口关联封装。主要是处理token值。
5 extract.yml存放token值。此外还需要读取extract.yml方法、写入extract.yml方法和清空extract.yml方法。
6 conftest.py文件作用在于运行all.py之前自动执行clear_extract_file()方法,到达每次运行all.py之前清空extract.yml的目的。
7 token值有可能放在url地址中,也有可能放在params中,也有可能放在请求头中。处理请求的方法放在request_util.py中,统一替换token值的办法。
8 yaml文件存放测试用例。
9 使用pytest.mark.parametrize()作为数据驱动。
10 使用debug_talk.py热加载的方式,实现动态参数。
11 反射,通过一个函数的字符串直接去调用这个方法。

  开发测试 最新文章
pytest系列——allure之生成测试报告(Wind
某大厂软件测试岗一面笔试题+二面问答题面试
iperf 学习笔记
关于Python中使用selenium八大定位方法
【软件测试】为什么提升不了?8年测试总结再
软件测试复习
PHP笔记-Smarty模板引擎的使用
C++Test使用入门
【Java】单元测试
Net core 3.x 获取客户端地址
上一篇文章      下一篇文章      查看所有文章
加:2022-08-06 11:12:24  更:2022-08-06 11:12:41 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/17 22:14:02-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码