软件测试 / 测试开发丨学习笔记之接口自动化测试
- 2023-05-29 北京
本文字数:9973 字
阅读完需:约 33 分钟
获取更多相关知识
本文为霍格沃兹测试开发学社学员学习笔记分享,文末附原文链接
一、接口请求体-文件
1、通过接口上传文件
辨别文件上传接口,查看 Content-Type
proxies
2、文件上传接口的场景
解决接口测试流程中文件上传的问题指定 name 指定 filename 指定 content-type
3、 使用 requests 上传
import requests
url = "https://httpbin.ceshiren.com/post"
# r = requests.post(url,
# # files参数用来解决文件上传接口
# files={"hogwarts_file": open("1.text", "rb")},
# proxies={"http": "http://127.0.0.1:8080",
# "https": "http://127.0.0.1:8080"},
# verify=False
# )
r = requests.post(url,
# files参数用来解决文件上传接口
# value通过元组传递,实现指定filename
files={"hogwarts_file": ("test.txt", open("1.text", "rb"))},
proxies={"http": "http://127.0.0.1:8080",
"https": "http://127.0.0.1:8080"},
verify=False
)
print(r.json())
二、接口请求体-form 表单
1、 FORM 请求
数据量不大
数据层级不深的情况
通常以键值对传递
proxies
2、 使用 FORM 请求
import requests
class TestForm:
def setup_class(self):
# 设置代理
self.proxy = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
def test_data(self):
url = "https://httpbin.ceshiren.com/post"
datas = {"a": 1}
# 通过data参数传入请求体信息
r = requests.post(url, data=datas, proxies=self.proxy, verify=False)
print(r.json())
三、接口请求体-xml
1、复杂数据解析
数据保存:将复杂的 xml 或者 json 请求体保存到文件模板中
数据处理:使用 mustache、freemaker 等工具解析简单的字符串替换使用 json、xml、api 进行结构化解析
数据生成:输出最终结果
四、xml 响应断言
1、 XML 介绍
可扩展标记语言(Extensible Markup Language)的缩写
也是一种结构化的数据
2、 XML 断言
from requests_xml import XMLSession
def test_xml():
# 设置session
session = XMLSession()
r = session.get("https://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss")
# 文本格式打印响应内容
print(r.text)
# links可以把响应页面中的所有链接提取出来,并返回一个列表
print(r.xml.links)
# raw_xml返回了字节形式的响应内容
print(r.xml.raw_xml)
# text 返回标签中的内容
print(r.xml.text)
3、 XPath 断言
from requests_xml import XMLSession
def test_xpath():
session = XMLSession()
r = session.get('https://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss')
# 第一个参数为xpath的表达式,first参数表示是否只返回第一个查找的结果,默认False 把所有找到的对象以列表返回,True 表示把查找到的第一个结果返回
item = r.xml.xpath('//link', first=True)
# 拿到Element对象
print(item)
# 调用text属性,拿到link标签下的内容
print(item.text)
# 提取全部,需要进行循环遍历
item1 = r.xml.xpath('//link')
# 定义一个列表,存放提取的内容
result = []
for i in item1:
# 拿到所有link标签的内容,是单个对象,不是列表
print(i.text)
# 返回的内容放在列表中
result.append(i.text)
# 打印列表内容
print(result)
assert "http://www.nasa.gov/" in result
4、 XML 解析
# as ET 起别名
import xml.etree.ElementTree as ET
from requests_xml import XMLSession
def test_xml_tree():
# 设置session
session = XMLSession()
r = session.get("https://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss")
# 自定义封装 xml 解析方法
# 调用fromstring方法,将提取的响应内容放到该方法里,得到整个根元素赋给root
root = ET.fromstring(r.text)
# 调用findall方法,使用xpath表达式,查找根元素
item = root.findall(".")
# 是一个element对象,根元素没有属性,只是遍历子元素的接口
print(item)
# 通过xpath查找子元素,查找link标签下的子元素
items = root.findall(".//link")
result = []
for i in items:
# # 拿到所有link标签的内容
# print(i.text)
# 返回的内容放在列表中
result.append(i.text)
# 打印列表内容
print(result)
assert "http://www.nasa.gov/" in result
五、cookie 处理
1、Cookie 简介
Cookie 使用场景在接口测试过程中,很多情况下,需要发送的请求附带 cookies,才会得到正常的响应的结果。所以使用 python+requests 进行接口自动化测试也是同理,需要在构造接口测试用例时加入 cookie
传递 Cookie 的两种方式通过请求头信息传递通过请求的关键字参数 cookies 传递
2、自定义 header
import requests
class TestReqCookie:
def setup_class(self):
# 设置代理
self.proxy = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
def test_req_header(self):
url = "https://httpbin.ceshiren.com/cookies"
header = {
"Cookie": "hogwarts=school",
"User-Agent": "hogwarts"
} # 注意:Cookie首字母要大写,且没有s
r = requests.get(url=url, headers=header, proxies=self.proxy, verify=False)
print(r.request.headers)
3、使用 cookies 参数传递
import requests
class TestReqCookie:
def setup_class(self):
# 设置代理
self.proxy = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
def test_req_cookie(self):
self.url = "https://httpbin.ceshiren.com/cookies"
header = {
"User-Agent": "hogwarts"
} # 注意:Cookie首字母要大写,且没有s
# 字典,键值对格式,可以传多条cookie
cookie_data = {
"hogwarts": "school",
"teacher": "AD"
}
r = requests.get(url=self.url, headers=header, cookies=cookie_data, proxies=self.proxy, verify=False)
print(r.request.headers)
六、超时处理
1、 为什么接口测试需要请求超时处理
2、设置超时处理
import requests
class TestReqTime:
def setup_class(self):
# 设置代理
self.proxy = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
def test_001(self):
# 通过timeout参数设置超时时间,参数对应的值通常是一个数字类型
r = requests.post('https://httpbin.ceshiren.com/post', timeout=3, proxies=self.proxy, verify=False)
print(r.json())
def test_002(self):
# 通过timeout参数设置超时时间,参数对应的值通常是一个数字类型
r = requests.post('https://httpbin.ceshiren.com/post', timeout=3, proxies=self.proxy, verify=False)
print(r.json())
def test_003(self):
# 通过timeout参数设置超时时间,参数对应的值通常是一个数字类型
r = requests.post('https://httpbin.ceshiren.com/post', timeout=0.01, proxies=self.proxy, verify=False)
print(r.json())
七、 代理配置
1、 使用代理之前
2、 使用代理之后
3、 代理在接口自动化的使用场景
测试脚本,更直观的排查请求错误,相当于编写代码时的 debug
获取没有错误的,真实的接口请求响应信息
通过代理获取自动化测试的请求响应
对比两次请求响应的区别
4、 Requests 代理
通过 proxies 参数,监听请求与响应信息
5、 如何使用代理
设定代理格式
通过 proxies 参数传递代理设置
开启代理工具监听请求
import requests
class TestProxt:
def setup_class(self):
# 定义一个代理的配置信息的变量,key值为协议,value 为代理工具的配置
self.proxy = {
"http": "http://127.0.0.1:8080",
"https": "http://127.0.0.1:8080"
}
def test_proxy(self):
# 通过proxies 传递代理配置
self.data = {"a": 1}
r = requests.post(url="https://httpbin.ceshiren.com/post", proxies=self.proxy, json=self.data, verify=False)
print(r.json())
八、多层嵌套响应断言
1、 多层嵌套结构
// - 层级多。
// - 嵌套关系复杂。
{
"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表达式)
JSONPath 与代码结合(Java)
2、 复杂场景响应提取
3、 JSONPath 简介
在 JSON 数据中定位和提取特定信息的查询语言。
JSONPath 使用类似于 XPath 的语法,使用路径表达式从 JSON 数据中选择和提取数据。
相比于传统的提取方式,更加灵活,并且支持定制化。
4、 JSONPath 对比
5、 JSONPath 如何使用
语法知识。
第三方库调用。
6、 JSONPath 语法
7、 JSONPath 练习
获取所有书籍的作者
$..book..author
获取所有作者
$..author
获取 store 下面的所有内容
$.store
获取所有的价格
$..price
获取第三本书 (从 0 开始)
$..book[2]
获取所有包含 isbn 的书籍
$..book[?(@.isbn)]
获取所有价格小于 10 的书
$..book[?(@.price<10)]
获取所有书籍的数量(使用 length 函数)
$..book.length
九、 宠物商店接口自动化测试实战
1、 实战思路
2、需求分析
宠物管理业务场景:添加宠物。查询宠物信息。修改宠物信息。删除宠物。
3、自动化测试脚本思路
自动化代码
"""
完成宠物商城宠物管理功能接口自动化测试。
编写自动化测试脚本。
完成复杂断言。
"""
import requests
class TestPetStore:
def setup_class(self):
# 定义pet_id
self.pet_id = 9223372036854259066
# 定义请求url
self.base_url = "https://petstore.swagger.io/v2/pet"
# 拼接宠物查询接口url
self.search_url = self.base_url + "/findByStatus"
# 删除接口
self.delete_url = self.base_url + f"/{self.pet_id}"
# 宠物状态
self.pet_status = "available"
# 新增宠物信息
self.add_pet_info = {
"id": self.pet_id,
"category": {
"id": 0,
"name": "string"
},
"name": "doggie",
"photoUrls": [
"string"
],
"tags": [
{
"id": 0,
"name": "string"
}
],
"status": self.pet_status
}
# 更新宠物数据
self.update_name = "maomao"
self.update_pet_info = {
"id": self.pet_id,
"category": {
"id": 0,
"name": "string"
},
"name": self.update_name,
"photoUrls": [
"string"
],
"tags": [
{
"id": 0,
"name": "string"
}
],
"status": self.pet_status
}
# 查询宠物数据
self.search_params = {
"status": self.pet_status
}
def test_pet_manager(self):
# 新增宠物接口
add_r = requests.post(self.base_url, json=self.add_pet_info)
assert add_r.status_code == 200
# 查询宠物接口
find_r = requests.get(self.search_url, params=self.search_params)
assert find_r.status_code == 200
# 修改宠物
update_r = requests.put(self.base_url, json=self.update_pet_info)
assert update_r.status_code == 200
# 删除宠物
delete_r = requests.delete(self.delete_url)
assert delete_r.status_code == 200
代码优化
配置代理查看接口数据
添加日志
使用 jsonpath 断言使用 jsonpath 实现多层嵌套响应的断言。
日志配置:log_utils.py
# 配置日志
import logging
import os
from logging.handlers import RotatingFileHandler
# 绑定绑定句柄到logger对象
logger = logging.getLogger(__name__)
# 获取当前工具文件所在的路径
root_path = os.path.dirname(os.path.abspath(__file__))
# 拼接当前要输出日志的路径
log_dir_path = os.sep.join([root_path, '..', f'/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)
# 设置日志的格式
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)
优化后完整代码
"""
完成宠物商城宠物管理功能接口自动化测试。
编写自动化测试脚本。
完成复杂断言。
"""
import allure
import jsonpath
import requests
from interface.utils.log_utils import logger
@allure.feature("宠物管理业务场景接口测试")
class TestPetStore:
def setup_class(self):
# 代码优化,配置代理查看接口数据
self.proxy = {
"http": "http://127.0.0.1:8888",
"https": "http://127.0.0.1:8888"
}
# 定义pet_id
self.pet_id = 9223372036854259969
# 定义请求url
self.base_url = "https://petstore.swagger.io/v2/pet"
# 拼接宠物查询接口url
self.search_url = self.base_url + "/findByStatus"
# 删除接口
self.delete_url = self.base_url + f"/{self.pet_id}"
# 宠物状态
self.pet_status = "available"
# 新增宠物信息
self.add_pet_info = {
"id": self.pet_id,
"category": {
"id": 0,
"name": "string"
},
"name": "doggie",
"photoUrls": [
"string"
],
"tags": [
{
"id": 0,
"name": "string"
}
],
"status": self.pet_status
}
# 更新宠物数据
self.update_name = "maomao"
self.update_pet_info = {
"id": self.pet_id,
"category": {
"id": 0,
"name": "string"
},
"name": self.update_name,
"photoUrls": [
"string"
],
"tags": [
{
"id": 0,
"name": "string"
}
],
"status": self.pet_status
}
# 查询宠物数据
self.search_params = {
"status": self.pet_status
}
@allure.story("宠物的增删改查场景测试")
def test_pet_manager(self):
# with allure.step添加步骤
with allure.step("新增宠物"):
# 新增宠物接口
add_r = requests.post(self.base_url, json=self.add_pet_info, proxies=self.proxy, verify=False)
# 添加日志
logger.info(f"新增宠物接口的响应为:{add_r.text}")
# 断言
assert add_r.status_code == 200
with allure.step("查询宠物"):
# 查询宠物接口
search_r = requests.get(self.search_url, params=self.search_params, proxies=self.proxy, verify=False)
# 添加日志
logger.info(f"查询宠物接口的响应为:{search_r.text}")
# 断言
assert search_r.status_code == 200
# 新增宠物业务断言,查询接口中是否包含新增宠物的id
# 使用jsonpath提取响应内容,find_r.json()查询结果转换为json对象
search_js = jsonpath.jsonpath(search_r.json(), "$..id")
assert self.pet_id in search_js
with allure.step("更新宠物"):
# 更新宠物
update_r = requests.put(self.base_url, json=self.update_pet_info, proxies=self.proxy, verify=False)
# 添加日志
logger.info(f"更新宠物接口的响应为:{update_r.text}")
# 断言
assert update_r.status_code == 200
# 再次查询宠物
search_r1 = requests.get(self.search_url, params=self.search_params, proxies=self.proxy, verify=False)
# 更新宠物的业务断言,查询接口中是否包含更新宠物的name
update_js = jsonpath.jsonpath(search_r1.json(), "$..name")
assert self.update_name in update_js
with allure.step("删除宠物"):
# 删除宠物
delete_r = requests.delete(self.delete_url, proxies=self.proxy, verify=False)
# 添加日志
logger.info(f"删除宠物接口的响应为:{delete_r.text}")
# 断言
assert delete_r.status_code == 200
# 再次查询宠物
search_r2 = requests.get(self.search_url, params=self.search_params, proxies=self.proxy, verify=False)
# 业务断言,查询接口中不包含宠物的id
delete_js = jsonpath.jsonpath(search_r2.json(), "$..id")
assert self.pet_id not in delete_js
生成测试报告
# 生成报告信息
pytest -vs test_petsearch_l2.py --alluredir=./reports/
# 生成报告在线服务,查看报告
allure serve ./reports/
版权声明: 本文为 InfoQ 作者【测试人】的原创文章。
原文链接:【http://xie.infoq.cn/article/46f186eddfe889b8ee36f56b3】。文章转载请联系作者。
测试人
专注于软件测试开发 2022-08-29 加入
霍格沃兹测试开发学社,测试人社区:https://ceshiren.com/t/topic/22284
评论