jsonpath:对json串进行搜索
安装jsonpath
安装:pip install jsonpath
导入: from jsonpath import jsonpath
jsonpath能通过简单的方式就能提取给定JSON中的字段。
jsonpath官方地址:https://goessner.net/articles/JsonPath/
在线检验jsonpath是否正确:https://www.jsonpath.cn/
结合2个网站,使用网站当中案例去练习。
jsonpath运算符 – 以$ 符号开头
$…price 获取json当中,key为price的所有值——》获取某个key的所有值,这个较为常用;=递归搜索
$.store.book[1].price——》一级一级去获取 更加精准
$…book[0] ——》 获取 多个值,可以用列表取值。
$…book[0:3] ——》可以使用列表的切片
$…book[0,1,2]——》获取第一个第二个 第三个
$…book[?(@.price > 10)]——》 ?过滤表达式== 价格大于10的显示出来
$…book. * ——》 匹配所有
Python的逻辑运算也可以在这里过滤表达式用:http://testingpai.com/article/1628759818085
"""
jsonpath(提取数据本身,jsonpath提取表达式$)
注意: jsonpath表示结果是一个列表 ,如果结果里有个值 放在列表里。
- 如果获取的是里面具体的值,列表取值[0]
要求: 数据必须是json格式数据。
"""
import requests
from jsonpath import jsonpath
# 1、登录
json = {"principal": "lemon_auto", "credentials": "lemon123456", "appType": 3, "loginType": 0}
url = 'http://shop.lemonban.com:8107/login'
res = requests.request(method="post",url=url, json=json)
print("登录的结果:", res.json())
access_token = jsonpath(res.json(),"$..access_token")[0] # jsonpath 提取数据
token_type =jsonpath(res.json(),"$..token_type")[0]
headers = {'Authorization': token_type + access_token}
# 2、搜索商品
url = 'http://shop.lemonban.com:8107/search/searchProdPage'
param = {"prodName":"真皮圆筒包"}
res = requests.request(method="get",url=url,params=param)
# print("搜索商品结果:",res.json()) # 获取json格式 转化为字典格式
prodId = res.json()["records"][0]["prodId"]
# 3、进入商品详情页
param = {"prodId":prodId}
url='http://shop.lemonban.com:8107/prod/prodInfo'
res = requests.request(method="get",url=url,params=param)
print("商品详情页的结果:",res.json())
skuId = res.json()["skuList"][0]["skuId"]
print(skuId)
# 4、添加购物车
json = {"basketId": 0, "count": 1, "prodId": prodId, "shopId": 1, "skuId": skuId}
url='http://shop.lemonban.com:8107/p/shopCart/changeItem'
res = requests.request(method="post",url=url, headers=headers, json=json)
print("添加购物车的结果:", res.text) # 这个接口返回值不是json格式 不能用json()方法转化字典,文本格式。
# 5、购物车查询 - 获取basketId的值
json = [] # 参数 空列表
url = 'http://shop.lemonban.com:8107/p/shopCart/info'
res = requests.request(method="post",url=url, headers=headers, json=json)
print("购物车查询的结果:", res.json())
# 注意: 这个地方就没有办法通过字典取值,因为不是字典,是列表
basketId = jsonpath(res.json(),"$..basketId")
print(basketId)
# 6、结算商品
json = {"addrId": 0, "basketIds": basketId, "couponIds": [], "isScorePay": 0, "userChangeCoupon": 0,
"userUseScore": 0, "uuid": "05d2b445-c062-4690-8eca-b24bd028e40f"}
url = 'http://shop.lemonban.com:8107/p/order/confirm'
res = requests.request(method="post",url=url, headers=headers, json=json)
print("结算商品的结果:", res.text)
接口自动化测试用例设计
pytest测试框架去测试接口: 对应用测试用例。【登录,搜索,购物车等】
-
针对每个模块设计测试用例 + 测试数据== 正常测试数据+ 异常测试数据,放在excel表格管理。
-
pytest的数据驱动 : 用例方法 执行所有数据。
这个大家做过测试,应该都知道接口用例的设计思路跟系统测试的用例设计是一样的。【等价类 边界值 场景法 错误推测法】
单接口场景 - 关注点是接口的各种参数组合
保障单接口的正确性,既要保证接口可以按照需求正确处理传入的参数,给出正确的返回;也要按照需求,正确的拒绝传入异常的参数,给出正确的拒绝性返回。
- 正向场景:正常发送请求得到正常的响应数据
- 异常场景:使用等价类和边界值的方法,设计测试用例,用不属于规定范围的数据去发送请求,检查服务器能否正常处理
- 参数异常:长度异常,数据类型异常,为空,重复,业务异常参数【比如用户注册非正常运营商的号码】
多接口业务场景【冒烟测试】 - 优势:价值更高
按照用户实际使用场景梳理接口业务,通过多个接口的串联组合调用完成业务逻辑,更加关注于业务流程是否能跑通。
自动化测试用例设计注意事项
-
每个表单一个模块,每个模块测试用例都可以单独运行,测试用例之间没有相互依赖;
-
一个模块用例可以包含一个或多个测试步骤(一个接口请求即为一个测试步骤)
-
一般单接口用例就是一个步骤
-
业务场景用例包含多个步骤: 有些前置步骤需要执行
-
所以,我们来写一下登录的测试用例:
以后在公司开发没有给接口文档的时候,我们就自己去抓包获取这个接口的需要的信息;
登录通过抓包:登录需要的参数:{“principal”: “lemon_auto”, “credentials”: “lemon123456”, “appType”: 3, “loginType”: 0}
用户名: 账号为4~16位字母、数字或下划线
设计测试数据
正常: 符合长度和数据类型要求的 正确的用户名
异常: 小于4 大于16,不是数字字母和下划线字符,为空等
密码: 是否正确
APPtype和logintype 我们固定,就测试这种类型。
利用前面所写的excel封装,pytest参数化,来完成接口测试用例数据读取,并发起请求
-
1、利用openpyxl库,从excel当中读取测试数据出来: 之前的接口数据都是手动写的,我们其实是在excel管理的,直接读取。
-
2、使用pytest框架来写用例: 结合数据驱动来实现多条测试用例的运行
-
3、在用例的步骤,发起http请求,然后获取响应结果
import json
import pytest
import requests
from tools.handle_excel import read_excel
from tools.handle_path import exc_path
# 第一步:调用excel操作哈拿书,读取excel数据
all_cases = read_excel(exc_path,"登录")
# 第二步: 使用pytest执行接口用例 + 发送接口请求
@pytest.mark.parametrize("data",all_cases)
def test_login_case(data):
method = data['请求方法']
url = data['接口地址']
headers = data['请求头']
params = data['请求参数'] # 字符串类型转换为字典
res = requests.request(method, url, json=params,headers=headers)
这个时候会成功么?-- 报错。: AttributeError: ‘str’ object has no attribute ‘items’。这是因为 从excel读取的数据看起来是字典,但是其实是字符串;但是接口发送的参数需要是字典的格式。所以需要数据格式的转化。
- eval()
- json.loads
json数据和序列化与反序列化
什么是json 【(JavaScript Object Notation, JavaScript 对象表示法】: json本质上是一种字符串,只是还要符合特殊格式的字符串
json - 表达数据。https://www.runoob.com/json/json-tutorial.html
JSON 数据的书写格式是: key : value ,看起来跟Python的字典很像,但是不是字典。
JSON语法规则:
- 数据在键值对中
- 多条数据由逗号分隔
- 花括号 {} 保存对象
- JSON 值【value】可以是:
- 数字(整数或浮点数)
- 字符串(在双引号中)
- 逻辑值(true 或 false)
- 中括号 [] 保存数组: 类似于Python列表,索引取值 从0开始
{
"hobby": ["游戏", "音乐", "电影"]
}
- null
json的语法要求和Python语法的区别:
-
字符串:双引号括号来的。------> python当中单双引号都可以。
- 因为很多其他的语言 双引号才能表示字符串 为了兼容其他语言,json里的字符串必须是双引号
- 布尔值:true,false。----------> python当中是True,False.
- null: 为空 ,------------> python当是None。
-
json不是字典,但是可以跟python的字典实现相互转化: 注意: 只能值字典类型 其他类型不可以
- 序列化 - python数据类型–> 成json串。
- 反序列化 - json字符串转成—> python数据类型。
-
json字符串与字典之间的转换:python的标准库是json,不需要安装 直接导入使用 处理json数据。
- json.loads() – json字符串转换成python字典。-- 反序列化操作方法 ===== 重点掌握
- json.dumps() – Python的字典转json字符串。–序列化的方法
evel和json转化各有优劣势 我们来对比一下:
json.loads():这个函数用于将JSON格式的字符串转换为字典。它只能处理符合JSON格式的字符串,如果字符串中包含其他类型的数据,可能会抛出异常。
eval():这个函数可以将字符串作为Python表达式进行求值,并返回结果。但是,由于它可能执行字符串中的任何代码,所以使用时需要特别小心,避免执行恶意代码。
参考文章:http://testingpai.com/article/1692240611928
json数据格式 : 特殊的字符串
a = {"a":"1"} #字典
print(type(a),a)
b = '{"a":"2"}' # json串
print(type(b),b)
c = "{'a':'1'}" # 普通的字符串 不是json数据 双引号
d = '{"a":true}'
e = '{"a":null}'
写自动化测试用例的数据设计为 json格式数据
– json.loads 统一转化。
- 避免eval(): 不符合Python格式,转化失败 – null true false
如果不是一个标准的json格式的字符串,用反序列化失败,报错信息如下:
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)
import json
params = "{'principal': 'lemon_py'}"
print(type(params),params)
# # 反序列化的操作 转化为 Python的字典
# params = json.loads(params)
# print(type(params),params)
# eval转化
params = eval(params)
print(type(params),params)
运行结果如下:
<class 'str'> {'principal': 'lemon_py'}
<class 'dict'> {'principal': 'lemon_py'}
断言 -测试用例的断言
测试用例执行步骤:
- 1、利用excel库,从excel当中读取测试数据出来
- 2、使用pytest框架来写用例
- 3、在用例的步骤,发起http请求,然后对响应结果
- 4、做断言。对响应状态码做断言。
思考:这个断言怎么做呢?
-
1、首先,能用整个响应结果的文本做断言么?
不可以,token是变化的 每次都不一样的
可以采取: 取一些固定的字段 断言数据-- nickname -
2、登录成功和失败的结果不一样,用pytest框架数据驱动执行,需要断言是如何统一做断言?
登录成功:响应结果是一个 json数据
登录失败:响应结果是一个文本
期望结果 用来做断言数据-- 格式统一化。 -
1、在excel表格中把文本结果和json结果统一断言方式
-
2、封装一个函数做断言,能处理不同的场景。
- 第一步: 先把excel读取的数据反序列化为字典
- 第二步: 拿到期望结果里的jsonpath表达式,取掉键值[k,v],v用来做断言,k用来取值。
- 登录成功情况覆盖
- 登录失败情况覆盖
- 第三步: 可以做个判断,登录成功和登录失败分别做不同处理
3、验证这个断言的方法是否适用于各个模块的用例。
"""
预期结果: 可能有以下两种形式:
{"$..nickName":"lemon_py"}
{"text":"Incorrect account or password"}
处理步骤:
第一步: 反序列化操作: 转化为字典
第二步: 取到期望结果键值对: key 是jsonpath 表示式,value是断言的预期结果
- 判断: key是$ 开头的json提取器,对login.json()去jsonpath提取
- 或者是text,login.text 执行结果 vs value
第三步: key用来从login的结果里提取数据的 -- 执行结果
第四步: value vs 执行结果数据 断言比对
"""
import json
from jsonpath import jsonpath
# expected = '{"$..nickName":"lemon_py"}' # 预期结果
expected = '{"text":"Incorrect account or password"}'
# login_result = {"access_token":"804d0382-b5b5-4950-bdcf-3f9376d84d27","token_type":"bearer","refresh_token":"29a0fc92-2578-4df2-8b00-3a6170fc480a",
# "expires_in":1295999,"pic":None,"userId":"8d0aa6ffd73a42a0acf5006817c0a564",
# "nickName":"lemon_py","enabled":True} # login_res.json()
login_result = "Incorrect account or password" # login_res.text
# 第一步: 反序列化操作: 转化为字典
expected = json.loads(expected)
# 第二步: 取到期望结果键值对: key 是jsonpath 表示式,value是断言的预期结果
for k,v in expected.items():
if k.startswith("$"):
# k是 "$..nickName",v 是lemon_py
actual_result = jsonpath(login_result,k)[0]
assert actual_result == v
elif k == 'text':
assert login_result == v
断言封装
"""
封装函数:一次封装 多次使用
步骤:
1、功能代码写出来
2、def 封装
3、参数化: 考虑数据是变化的 设置为参数
4、判断是否返回值 :不需要设置返回值
"""
import json
from jsonpath import jsonpath
def response_asser(expected_data,login_resp):
"""
这是做响应断言的函数
:param expected_data: 从excel表格里读取的预期结果表达式
:param login_resp:登录的响应消息
:return:
"""
# 第一步: 反序列化操作: 转化为字典
expected = json.loads(expected_data)
# 第二步: 取到期望结果键值对: key 是jsonpath 表示式,value是断言的预期结果
for k,v in expected.items():
if k.startswith("$"):
# k是 "$..nickName",v 是lemon_py
actual_result = jsonpath(login_resp.json(),k)[0]
assert actual_result == v
elif k == 'text':
assert login_resp.text == v
requests库的二次封装
登录接口和搜索接口发送接口请求有太多雷同的代码,后面模块多了更多雷同;这种代码的冗余 我们需要解决一下,进行请求方法的二次封装。
- 我们测试用例的脚本里尽量简洁的-- 功能测试人员进行测试用例模块维护,代码越少好。
- 再次封装requests方法,把通用代码封装+考虑到各种请求情况 ,不需要写用例的改参数。 简化功能测试工作。
- 希望这个统一的方法里能覆盖各种参数的传递方式
- json参数
- formdata参数
- 文件参数等各种参数类型都能覆盖到
- 希望这个统一的方法里能覆盖各种请求方法
- get post put 等
- 这样不管什么请求接口都可以直接调用同一个请求方法方法做处理。
"""
* 希望这个统一的方法里能覆盖各种参数的传递方式
* json参数
* formdata参数
* 文件参数等各种参数类型都能覆盖到
* 希望这个统一的方法里能覆盖各种请求方法
* get post put 等
传参的方式:
1、get请求,params 接受
2、post请求:content-type的类型有关系。
- application/json格式数据,json关键字接受参数,content-type的头部不需要传 默认就是json
- application/x-www-form-urlencoded,data关键字接受参数,content-type的头部不需要传 默认就是form
- multipart/form-data:支持文件/图片等传输 ,一般都是文件 图片上传接口
- 发送请求的时候不能带上 'Content-Type': 'multipart/form-data' 删除之后才发送接口请求。
步骤:
1、def 函数封装: 因为针对excel读取数据-- 头部 参数 url 方法等-- 参数数据参数化。
2、参数和头部 因为读取出来是字符串,所以都要做反序列的操作
3、接口请求可能是get post put等各种请求方法 分支判断
4、有些接口有头部 有些接口没有头部?-- 做判空处理的
5、post方法的数据格式- content-type有多种情况 所以都要覆盖。
6、当遇到文件类型接口的时候: 文件参数如何处理?-- 不能写死:
- 文件名字 : 作为测试用例数据写在excel里,方便后续做数据维护 改excel 不需要改代码。
- 文件路径处理: 统一的路径处理 handle_path
"""
import json
import requests
from tools.handle_path import pic_path
def requests_api(casedata):
method = casedata["请求方法"]
url = casedata["接口地址"]
headers = casedata["请求头"]
params = casedata["请求参数"]
# 反序列操作: 结合判空处理,
if headers is not None:
headers = json.loads(headers)
if params is not None:
params = json.loads(params)
#接口请求可能是get post put等各种请求方法 分支判断
if method.lower() == "get":
resp = requests.request(method=method, url=url, params=params,headers=headers)
elif method.lower() == "post":
# post请求:content-type的类型有关系。需要对每一种类型做处理 分支判断
if headers["Content-Type"] == "application/json":
resp = requests.request(method=method, url=url, json=params, headers=headers)
if headers["Content-Type"] == "application/x-www-form-urlencoded":
resp = requests.request(method=method, url=url, data=params, headers=headers)
if headers["Content-Type"] == "multipart/form-data":
# 发送请求的时候不能带上 'Content-Type': 'multipart/form-data' 删除之后才发送接口请求。
headers.pop("Content-Type") # 字典删除元素
filename = params["filename"] # 文件名字 值
file_obj = {"file": (filename, open(pic_path/filename, "rb"))} # 文件参数
resp = requests.request(method=method, url=url,headers=headers,files=file_obj)
elif method.lower() == "put":
pass
return resp
接口关联和前置的应用 --单接口测试
解决刚刚的遗留问题: 就是上传接口是需要先登录的,需要传一个authorization的token的头部信息;我们把他写死的;但是这个token 其实是会过期的,而且每个用户登录都是不一样的。所以,应该是要动态获取的。
- 也就实现执行上传接口后,都需要先登录,获取token。
- 那么这个可以把登录设置为上传接口的前置件。使用pytest的conftest前置。
注意: 前置比较多的单接口测试 或者 业务流 冒烟测试 是不适合用conftest前置进行操作。
多个前置和业务流的自动化测试,需要用 【多接口的动态参数处理】
conftest.py文件如下
"""
执行一个接口执行之前 先要执行另外一个接口 获取数据【token】 给到下一个使用:
- pytest的夹具
- yield 返回值
- conftest 共享
定义夹具 获取token
"""
import pytest
import requests
from jsonpath import jsonpath
@pytest.fixture()
def login_fixture():
url_login = "http://shop.lemonban.com:8107/login"
param = {"principal": "lemon_py", "credentials": "12345678", "appType": 3, "loginType": 0}
resp = requests.request("post",url=url_login,json=param)
access_token = jsonpath(resp.json(), "$..access_token")[0] # jsonpath 提取数据
token_type = jsonpath(resp.json(), "$..token_type")[0]
token = token_type+access_token
yield token # 夹具的返回值
什么叫做多接口的动态参数处理?
- 测试数据都放在excel中管理,每个接口如果有提取的数据就直接放在excel里写好,到时候直接读取出来做响应的提取操作即可。
- 电商项目: 购物车 token proId skuID等 需要执行多个接口 获取返回值;
- 业务流: 前面过很多步骤 【中间步骤都是单独的一个接口的请求】
以上情况 都不太适合用conftest夹具测试使用。因为前置很多 处理比较麻烦。
提取出来的数据存放在哪里?以及怎么传给下个接口使用?
参考postman的处理方法。
- 先执行前置登录-- 正常接口测试
- 执行之后,提取数据–
- 存在环境变量— 共享的后面每个接口都可以调用数据,变量
- 后面接口调用 – {{变量}} === 这个是{{}是一个占位符,用你从环境变量里取到值替换掉这个位置的数据 【Jmeter - ${}】
设计的思路:业务流: 登录-搜索-进入详情页-添加购物车-查询购物车-结算-提交订单
- 1、先把业务流的接口用例都写出来,在excel表格里统一管理
- 2、关联的接口 需要提取的数据也提取出来: 加一个提取响应字段在excel里,用jsonpath提取
- 3、代码提取出来后,保存在环境变量里 == 类 动态属性
- 4、其他接口要用的位置用占位符表示,后面用代码替换成为 -环境变量里存的值。
动态参数具体看下一章节