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/json | json格式传参 | row中的application/plain | plain格式传参 | row中的application/javascript | javascript格式传参 | row中的application/html | html格式传参 | row中的application/xml | xml格式传参 | binary | 二进制流的数据 |
1.1.3 request()方法参数
requests库中的request()方法参数,如下表所示:
参数 | 描述 |
---|
method | 请求方式 | url | 请求路径 | params | get方式传参 | data | post,put,patch传参 | headers | 请求头 | cookies | 请求的cookie信息 | files | 文件上传 | json | post传参 | auth | 鉴权 | timeout | 超时 | allow_redirects | 重定向 | proxies | 代理 | hooks | 钩子方法 | stream | 文件下载 | verify | 是否需要验证证书 | cert | CA证书 |
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"
}
respones = requests.get(url=url,params=params)
print(respones.text)
print(respones.content)
print(respones.json())
print(respones.status_code)
print(respones.reason)
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"
}
respones = requests.get(url=url,params=params)
return_data = respones.json()
print(return_data)
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):
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))
}
}
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
obj = re.search('name="csrf_token" value="(.*?)"',return_data)
TestRequests.csrf_token = obj.group(1)
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 = 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
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 = requests.session()
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=""
def test_get(self):
url = "https://api.weixin.qq.com/cgi-bin/token"
params ={
"grant_type":"client_credential",
"appid":"wx2c4830ee4aa8ffa9",
"secret": "f889ab43e8fafc488bd905b14f464d08"
}
res = RequestsUtil().send_request(method="get",url=url,params=params)
return_data = res.json()
print(return_data)
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]
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的方法,如下所示:
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]
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)
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):
str = json.dump(data)
else:
str = data
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测试用例文件的方法,如下所示:
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:
def analysis_yaml(self,caseinfo):
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):
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提取,如下图所示:
综上,代码如下:
if "extract" in caseinfo_keys:
for key,value in dict(caseinfo["extract"]).items():
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:
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):
str_data = json.dumps(data)
else:
str_data = 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)
最后在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(','))
new_value = getattr(DebugTalk(),func_name)(*args_value.split(','))
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):
str_data = json.dumps(data)
else:
str_data = 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)
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(','))
new_value = getattr(DebugTalk(),func_name)(*args_value.split(','))
print(new_value)
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:
@pytest.mark.parametrize("caseinfo", read_testcase_file("/testcases/get_token.yml"))
def test_get_token(self,caseinfo):
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:
'''
flag = 0
if planned_result and isinstance(planned_result,list):
for i in planned_result:
for key, value in dict(i).items():
if key=="equals":
for assert_key,assert_value in dict(value).items():
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类型断言
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:
'''
flag = 0
if planned_result and isinstance(planned_result,list):
for i in planned_result:
for key, value in dict(i).items():
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 = requests.session()
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 反射,通过一个函数的字符串直接去调用这个方法。
|