目录:
- 接口请求体-文件
- 文件上传接口场景
- 使用 requests 如何上传
- 接口请求体-form表单
- 什么是 FORM 请求
- 如何使用?
- 接口请求体-xml
- xml响应断言
- 什么是 XML
- XML 断言
- XPath 断言
- XML 解析
- cookie处理
- Cookie简介
- 超时处理
- 请求超时
- 为什么接口测试需要请求超时处理?
- 如何设置
- 代理配置
- 使用代理之前
- 使用代理之后
- 代理在接口自动化的使用场景
- Requests 代理
- 如何使用
- 多层嵌套响应断言
- 什么是多层嵌套结构
- 复杂场景响应提取
- JSONPath 简介
- JSONPath 对比
- JSONPath 如何使用
- JSONPath 语法
- JSONPath 的练习环境
- JSONPath 练习题目
- JSONPath 练习
- JSONPath 与代码结合(Python)
- 宠物商店接口自动化测试实战
- 被测产品
- 需求说明
- 相关知识点
- 实战思路
- 需求分析
- 宠物管理接口业务流程测试用例
- 编写自动化测试脚本思路
- 编写自动化测试脚本
- 脚本优化 - 配置代理查看接口数据
- 脚本优化 - 添加日志
- 脚本优化 - 使用 jsonpath 断言
- 生成测试报告
- 总结
- 项目结构:
- 项目地址:
1.接口请求体-文件
文件上传接口场景
- 1867 文档中说明文件上传是一种常见的需要求,但是使用 html 中的 form 表单格式却不支持,
- 提出了一种兼容此需求的 mime type。
- 解决接口测试流程中文件上传的问题
- 指定 name
- 指定 filename
- 指定 content-type
使用 requests 如何上传
import requests
# key值需要替换为文件名
# value 对应为文件二进制流
def test_file():
r = requests.post("https://httpbin.ceshiren.com/post",
# files={"request_file": open("./datas/1.txt", "rb")},
files={"request_file": ("hello.txt", open("./datas/1.txt", "rb"))},
verify=False
)
print(r.text)
抓包看请求体:(学会使用charles或者fiddler抓包,看请求和响应)
2.接口请求体-form表单
什么是 FORM 请求
- 数据量不大
- 数据层级不深的情况
- 通常以键值对传递
如何使用?
import requests
def test_form():
data = {"hello": "form"}
r = requests.post("https://httpbin.ceshiren.com/post", verify=False, data=data)
print(r.text)
def test_json():
data = {"hello": "form"}
r = requests.post("https://httpbin.ceshiren.com/post", verify=False, json=data)
print(r.text)
3.接口请求体-xml
import requests
import xml.etree.ElementTree as ET
def test_xml():
# 读取XML文件
tree = ET.parse('./datas/test.xml')
root = tree.getroot()
# 创建一个空列表来保存数据
datas = []
# 遍历XML文件的每个元素
for elem in root.iter():
# 将元素的标签和文本内容添加到列表中
datas.append((elem.tag, elem.text))
data = datas
r = requests.post("https://httpbin.ceshiren.com/post", verify=False, data=data)
print(r.text)
运行抓包:
4.xml响应断言
什么是 XML
- 可扩展标记语言(Extensible Markup Language)的缩写
- 也是一种结构化的数据
XML 断言
from requests_xml import XMLSession
def test_xml():
session = XMLSession()
r = session.get('https://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss')
print(r.text)
print(r.xml.links)
print(r.raw_xml)
print(r.xml.text)
XPath 断言
from requests_xml import XMLSession
session = XMLSession()
r = session.get('https://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss')
r.xml.links
item = r.xml.xpath('//item', first=True)
print(item.text)
from requests_xml import XMLSession
import xml.etree.ElementTree as ET
def test_xml():
session = XMLSession()
r = session.get('https://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss')
print(r.text)
print(r.xml.links)
print(r.xml.raw_xml)
print(r.xml.text)
def test_xpath_assert():
session = XMLSession()
r = session.get('https://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss')
item = r.xml.xpath('//link')
result = []
for i in item:
print(i.text)
result.append(i.text)
print(result)
assert "https://www.nasa.gov/image-of-the-day/" in result
XML 解析
import xml.etree.ElementTree as ET
root = ET.fromstring(countrydata)
root.findall(".")
root.findall("./country/neighbor")
root.findall(".//year/..[@name='Singapore']")
root.findall(".//*[@name='Singapore']/year")
root.findall(".//neighbor[2]")
from requests_xml import XMLSession
import xml.etree.ElementTree as ET
def test_xml():
session = XMLSession()
r = session.get('https://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss')
print(r.text)
print(r.xml.links)
print(r.xml.raw_xml)
print(r.xml.text)
def test_xpath_assert():
session = XMLSession()
r = session.get('https://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss')
item = r.xml.xpath('//link')
result = []
for i in item:
print(i.text)
result.append(i.text)
print(result)
assert "https://www.nasa.gov/image-of-the-day/" in result
def test_xml_assert():
session = XMLSession()
r = session.get('https://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss')
root = ET.fromstring(r.text)
item = root.findall(".")
print(item)
items = root.findall(".//link")
print(items)
for i in items:
print(i.text)
5.cookie处理
Cookie简介
Cookie使用场景,在接口测试过程中,很多情况下,需要发送的请求附带cookies,才会得到正常的响应的结果。所以使用python+requests进行接口自动化测试也是同理,需要在构造接口测试用例时加入cookie。传递Cookie的两种方式
- 通过请求头信息传递
- 通过请求的关键字参数cookies传递
import requests
def test_cookie1():
url = "https://httpbin.testing-studio.com/cookies"
header = {"Cookie": "LiMing=teacher", "User-Agent": "hdc"}
r = requests.get(url=url, headers=header, verify=False)
print(r.request.headers)
def test_cookie2():
url = "https://httpbin.testing-studio.com/cookies"
header = {"User-Agent": "hdc"}
cookie_data = {"today": "20231013"}
r = requests.get(url=url, headers=header, verify=False, cookies=cookie_data)
print(r.request.headers)
6.超时处理
请求超时
为什么接口测试需要请求超时处理?
不设置请求超时:
设置请求超时:
如何设置
import requests
class TestReq:
def test_timeout(self):
r = requests.get('http://github.com', timeout = 0.01)
代码示例:
import requests
class TestReq:
def test_timeout(self):
r = requests.get('http://github.com', timeout=0.01)
# r = requests.get('http://github.com', timeout=5000)
7.代理配置
使用代理之前
使用代理之后
代理在接口自动化的使用场景
- 测试脚本,更直观的排查请求错误,相当于编写代码时的 debug
- 获取没有错误的,真实的接口请求响应信息
- 通过代理获取自动化测试的请求响应
- 对比两次请求响应的区别
Requests 代理
通过 proxies 参数,监听请求与响应信息
如何使用
- 设定代理格式
- 通过 proxies 参数传递代理设置
- 开启代理工具监听请求
import requests
def test_proxy():
proxy = {
"http": "http://127.0.0.1:6666",
"https": "http://127.0.0.1:6666"
}
url = "https://httpbin.testing-studio.com/post"
r = requests.post(url=url, proxies=proxy, verify=False)
print(r.text)
8.多层嵌套响应断言
什么是多层嵌套结构
// - 层级多。
// - 嵌套关系复杂。
{
"errcode": 0,
"errmsg": "ok",
"userid": "zhangsan",
"name": "张三",
"department": [1, 2],
"order": [1, 2],
"position": "后台工程师",
"mobile": "13800000000",
"gender": "1",
"email": "zhangsan@gzdev.com",
"biz_mail": "zhangsan@qyycs2.wecom.work",
"is_leader_in_dept": [1, 0],
"direct_leader": ["lisi", "wangwu"],
"avatar": "http://wx.qlogo.cn/mmopen/ajNVdqHZLLA3WJ6DSZUfiakYe37PKnQhBIeOQBO4czqrnZDS79FH5Wm5m4X69TBicnHFlhiafvDwklOpZeXYQQ2icg/0",
"thumb_avatar": "http://wx.qlogo.cn/mmopen/ajNVdqHZLLA3WJ6DSZUfiakYe37PKnQhBIeOQBO4czqrnZDS79FH5Wm5m4X69TBicnHFlhiafvDwklOpZeXYQQ2icg/100",
"telephone": "020-123456",
"alias": "jackzhang",
"address": "广州市海珠区新港中路",
"open_userid": "xxxxxx",
"main_department": 1,
"extattr": {
"attrs": [
{
"type": 0,
"name": "文本名称",
"text": {
"value": "文本"
}
},
{
"type": 1,
"name": "网页名称",
"web": {
"url": "http://www.test.com",
"title": "标题"
}
}
]
},
"status": 1,
"qr_code": "https://open.work.weixin.qq.com/wwopen/userQRCode?vcode=xxx",
"external_position": "产品经理",
"external_profile": {
"external_corp_name": "企业简称",
"wechat_channels": {
"nickname": "视频号名称",
"status": 1
},
"external_attr": [
{
"type": 0,
"name": "文本名称",
"text": {
"value": "文本"
}
},
{
"type": 1,
"name": "网页名称",
"web": {
"url": "http://www.test.com",
"title": "标题"
}
},
{
"type": 2,
"name": "测试app",
"miniprogram": {
"appid": "wx8bd80126147dFAKE",
"pagepath": "/index",
"title": "my miniprogram"
}
}
]
}
}
复杂场景响应提取
场景 | 方式 |
---|---|
提取 errcode 对应的值 | res["errcode"] |
提取 title 对应的值 | res["extattr"]["external_profile"]["external_attr"][1]["web"]["title"] |
提取 type 为 0 的 name | 编码实现 |
提取 attrs 下的所有的 name | 编码实现 |
JSONPath 简介
- 在 JSON 数据中定位和提取特定信息的查询语言。
- JSONPath 使用类似于 XPath 的语法,使用路径表达式从 JSON 数据中选择和提取数据。
- 相比于传统的提取方式,更加灵活,并且支持定制化。
JSONPath 对比
场景 | 对应实现 | JSONPath 实现 |
---|---|---|
提取 errcode 对应的值 | res["errcode"] | $.errcode |
提取 title 对应的值 | res["extattr"]["external_profile"]["external_attr"][1]["web"]["title"] 等 | $..title |
提取 type 为 0 的 name | 编码实现 | $..external_attr[?(@.type==0)].name |
提取 attrs 下的所有的 name | 编码实现 | $..attrs..name |
JSONPath 如何使用
- 语法知识。
- 第三方库调用。
JSONPath 语法
符号 | 描述 |
---|---|
$ | 查询的根节点对象,用于表示一个 json 数据,可以是数组或对象 |
@ | 过滤器(filter predicate)处理的当前节点对象 |
* | 通配符 |
. | 获取子节点 |
.. | 递归搜索,筛选所有符合条件的节点 |
?() | 过滤器表达式,筛选操作 |
[start:end] | 数组片段,区间为[start,end),不包含 end |
[A]或[A,B] | 迭代器下标,表示一个或多个数组下标 |
JSONPath 的练习环境
https://jsonpath.hogwarts.ceshiren.com/
{
"store": {
"book": [
{
"category": "reference",
"author": "Nigel Rees",
"title": "Sayings of the Century",
"price": 8.95
},
{
"category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
},
{
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
},
{
"category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
}
],
"bicycle": {
"color": "red",
"price": 19.95
}
},
"expensive": 10
}
JSONPath 练习题目
- 获取所有书籍的作者
- 获取所有作者
- 获取 store 下面的所有内容
- 获取所有的价格
- 获取第三本书
- 获取所有包含 isbn 的书籍
- 获取所有价格小于 10 的书
- 获取所有书籍的数量
JSONPath 练习
需求 | JsonPath |
---|---|
所有书籍的作者 | $.store.book[*].author |
所有作者 | $..author |
store 下面的所有内容 | $.store. |
所有的价格 | $.store..price |
第三本书 | $..book[2] |
所有包含 isbn 的书籍 | $..book[?(@.isbn)] |
所有价格小于 10 的书 | $.store.book[?(@.price < 10)] |
所有书籍的数量 | $..book.length |
JSONPath 与代码结合(Python)
- 环境安装:
pip install jsonpath
# 具体的使用。
jsonpath.jsonpath(源数据对象, jsonpath表达式)
代码示例:
import jsonpath
import requests
def test_jsonpath():
url = "https://httpbin.ceshiren.com/post"
req_body = {"teacher": "ad", "school": "hdc"}
r = requests.post(url, json=req_body, verify=False)
print(r.json()["headers"]["Content-Type"])
res = jsonpath.jsonpath(r.json(), "$..Content-Type")
print(res)
assert res[0] == r.json()["headers"]["Content-Type"]
9.宠物商店接口自动化测试实战
被测产品
- PetStore 宠物商城:
- 一个在线的小型的商城。
- 主要提供了增删查改等操作接口。
- 结合 Swagger 实现了接口的管理。
需求说明
- 完成宠物商城宠物管理功能接口自动化测试。
- 编写自动化测试脚本。
- 完成复杂断言。
相关知识点
形式 | 章节 | 描述 | |
---|---|---|---|
知识点 | 代理配置 | 利用代理分析测试脚本,排查请求错误 | |
知识点 | 多层嵌套响应断言 | 利用 jsonpath 进行多层嵌套的响应断言 |
实战思路
需求分析
- 被测产品:宠物商店系统 - 宠物管理。
- 宠物商店接口文档:https://petstore.swagger.io/
- 宠物管理业务场景:
- 添加宠物。
- 查询宠物信息。
- 修改宠物信息。
- 删除宠物。
宠物管理接口业务流程测试用例
编写自动化测试脚本思路
编写自动化测试脚本
import requests
class TestPetstorePetmanager:
def setup_class(self):
self.base_url = "https://petstore.swagger.io/v2/pet"
self.search_url = self.base_url + "/findByStatus"
self.pet_id = 9223372000001084222
self.delete_url = self.base_url + f'/{self.pet_id}'
self.pet_status = "available"
self.add_pet_info = {
"id": self.pet_id,
"category": {
"id": 1,
"name": "cat"
},
"name": "miao",
"photoUrls": [
"string"
],
"tags": [
{
"id": 5,
"name": "cute"
}
],
"status": self.pet_status
}
self.update_name = "miao-hdc"
self.update_pet_info = {
"id": self.pet_id,
"category": {
"id": 1,
"name": "cat"
},
"name": self.update_name,
"photoUrls": [
"string"
],
"tags": [
{
"id": 5,
"name": "cute"
}
],
"status": self.pet_status
}
self.search_param = {
"status": self.pet_status
}
def test_pet_manager(self):
# 新增宠物接口
add_r = requests.post(self.base_url, json=self.add_pet_info, verify=False)
# 查询宠物
search_r = requests.get(self.search_url, params=self.search_param, verify=False)
# 修改宠物
update_r = requests.put(self.base_url, json=self.update_pet_info, verify=False)
# 删除宠物
delete_r = requests.delete(self.delete_url, verify=False)
脚本优化 - 配置代理查看接口数据
- 在脚本中配置代理。
- 抓包查看接口测试中的接口请求和响应数据。
proxy = {
"http": "http://127.0.0.1:8888",
"https": "http://127.0.0.1:8888"
}
requests.post(url, json=pet_info, proxies=proxy, verify=False)
脚本优化 - 添加日志
- 新建日志配置。
- 在用例中使用配置好的日志实例。
import logging
import os
from logging.handlers import RotatingFileHandler
# 绑定绑定句柄到logger对象
logger = logging.getLogger(__name__)
# 获取当前工具文件所在的路径
root_path = os.path.dirname(os.path.abspath(__file__))
# 拼接当前要输出日志的路径
root_len = len(root_path)
strs = root_path[0:root_len-6]
log_dir_path = os.sep.join([strs,f'datas\logs'])
if not os.path.isdir(log_dir_path):
os.mkdir(log_dir_path)
# 创建日志记录器,指明日志保存路径,每个日志的大小,保存日志的上限
file_log_handler = RotatingFileHandler(os.sep.join([log_dir_path, 'log.log']),
maxBytes=1024 * 1024, backupCount=10,
encoding="utf-8")
# 设置日志的格式
date_string = '%Y-%m-%d %H:%M:%S'
formatter = logging.Formatter(
'[%(asctime)s] [%(levelname)s] [%(filename)s]/[line: %(lineno)d]/[%(funcName)s] %(message)s ',
date_string)
# 日志输出到控制台的句柄
stream_handler = logging.StreamHandler()
# 将日志记录器指定日志的格式
file_log_handler.setFormatter(formatter)
stream_handler.setFormatter(formatter)
# 为全局的日志工具对象添加日志记录器
# 绑定绑定句柄到logger对象
logger.addHandler(stream_handler)
logger.addHandler(file_log_handler)
# 设置日志输出级别
logger.setLevel(level=logging.INFO)
def prit_path():
print(root_path)
print(log_dir_path)
prit_path()
脚本优化 - 使用 jsonpath 断言
- 使用 jsonpath 实现多层嵌套响应的断言。
jsonpath.jsonpath(r.json(), "$..id")
import jsonpath
import requests
from interface_automation_testing.接口自动化测试_L2.宠物商店接口自动化测试实战.utils.log_util import logger
class TestPetstorePetmanager:
def setup_class(self):
self.base_url = "https://petstore.swagger.io/v2/pet"
self.search_url = self.base_url + "/findByStatus"
self.pet_id = 9223372000001084222
self.delete_url = self.base_url + f'/{self.pet_id}'
self.pet_status = "available"
self.add_pet_info = {
"id": self.pet_id,
"category": {
"id": 1,
"name": "cat"
},
"name": "miao",
"photoUrls": [
"string"
],
"tags": [
{
"id": 5,
"name": "cute"
}
],
"status": self.pet_status
}
self.update_name = "miao-hdc"
self.update_pet_info = {
"id": self.pet_id,
"category": {
"id": 1,
"name": "cat"
},
"name": self.update_name,
"photoUrls": [
"string"
],
"tags": [
{
"id": 5,
"name": "cute"
}
],
"status": self.pet_status
}
self.search_param = {
"status": self.pet_status
}
def test_pet_manager(self):
# 新增宠物接口
add_r = requests.post(self.base_url, json=self.add_pet_info, verify=False)
logger.info(f"新增宠物接口响应为:{add_r.text}")
assert add_r.status_code == 200
# 查询宠物
search_r = requests.get(self.search_url, params=self.search_param, verify=False)
logger.info(f"查询宠物接口的响应为:{search_r.text}")
assert search_r.status_code == 200
assert self.pet_id in jsonpath.jsonpath(search_r.json(), "$..id")
# 修改宠物
update_r = requests.put(self.base_url, json=self.update_pet_info, verify=False)
logger.info(f"修改宠物接口的响应为:{update_r.text}")
assert update_r.status_code == 200
search_r = requests.get(self.search_url, params=self.search_param, verify=False)
assert search_r.status_code == 200
assert self.update_name in jsonpath.jsonpath(search_r.json(), "$..name")
# 删除宠物
delete_r = requests.delete(self.delete_url, verify=False)
logger.info(f"删除宠物接口的响应为:{delete_r.text}")
assert delete_r.status_code == 200
search_r = requests.get(self.search_url, params=self.search_param, verify=False)
assert search_r.status_code == 200
assert self.pet_id not in jsonpath.jsonpath(search_r.json(), "$..id")
生成测试报告
- 安装 allure 相关依赖。
# 生成报告信息
pytest --alluredir=./report
# 生成报告在线服务,查看报告
allure serve ./report/
pytest -vs .\test_petstore.py --alluredir=./datas/report --clean-alluredir
allure serve ./datas/report
运行结果:
总结
- 通过 Swagger 文档获取接口信息。
- 使用 Requests 发出请求。
- 添加代理,抓包查看接口请求和响应数据。
- 使用 Jsonpath 提取复杂结构响应数据,然后进行断言。
- 添加 Log 日志。
- 生成 Allure 测试报告。
项目结构:
项目地址:
https://gitee.com/coderPatrickStar/template/tree/master/python_software_testing