【接口自动化】-1- 初识接口
一、什么是接口
接口涉及到四个实体:(我去饭店点餐)
我是客人 :客户端
厨师:服务器
服务员:接口
菜单:接口文档
接口定义了一套信息规则让两个系统之间互相不必知道对方的内部,只需通过接口就能进行交互
API应用编程接口:各个应用(程序之间进行高效的沟通与协作)
二、HTTP接口
基于HTTP协议的接口
基本特点
- 交互简单:请求 -> 响应模式,无状态
- 结构简单:请求、响应,都是:行、头、体
- 内容简单:除了体,其他均为 ASCII 内容
- 应用广泛:至于体,支持 JSON、HTML、XML、HTML、图片、视频、音乐
- 跨平台兼容性:电脑、电视、手机、智能家居
有冒号的是头 头完了两个换行是体
请求
行:方法(GET POST等)
头:描述(修饰)体
体:无限可能
响应
行:状态码
头:描述(修饰)体
缓存:CacheControl, Last-Modified
内容:Content-Type,Content-Length
体:无限可能
三、 接口测试工具实操
fiddler 的测试步骤:
1. 配置代理,使 Fiddler 录制请求
2. 点击 Replay and Edit,修改参数:构造的测试用例
3. 点击 Run、Start,发送修改后的请求,得到新的响应
优势:
- 基于真实的请求,有连贯业务场景
- 进行底层的精细化的操作
- 配套的相关请求,由客户端完成,降低使用门槛
高颜值的 半自动化的接口测试工具:postman
完全自动化就是自己写python代码啦
四、自动化测试的流程
-
分析文档(理解需求)
- 核心功能
- 登录:
- 方法:post
- url: http://api.fbi.com:9225/rest-v2/login/access_token
- body:
- password
- 创建任务:
- 方法:post
- url: http://api.fbi.com:9225/rest-v2/todo
- body:
- title(选填)
- is_done(选填)
- 查询任务列表
- 方法:get
- url: http://api.fbi.com:9225/rest-v2/todo
- 参数:
- page(选填)
- size(选填)
- 删除任务:
- 方法:delete
- url: http://api.fbi.com:9225/rest-v2/todo/{todo_id}
- 参数:
- todo_id
- 登录:
- 次要功能
- 辅助功能
- 理解:
- 有些接口有安全性要求:需要先登录再使用
- 任务 id:需要动态获取(接口关联)
- 核心功能
2. 设计用例
- 用例标题:验证登录账号可删除任务
- 前置条件:
- 已登录(得到了身份信息)
- 有任务(已经创建待删除的任务)
- 测试步骤:
- 得到任务 ID
- 调用删除接口(结果 1)
- 调用查询接口(结果 2)
- 断言(验证结果):
- 结果 1 成功
- 结果 2 失败
3. 数据准备
可登录的账号( bf@qq.com;bf123456 ) 可测试的任务
数据驱动测试的数据⽂件
4. 编写脚本
- 1. 纯代码脚本
- 2. 框架的⽤例
5. 执⾏步骤
6. 断⾔结果
五、接口客户端 requests
HTTP 协议客户端事实上的标准:
- 不支持异步
- 不支持 HTTP2
pip install requests pytest jsonptah -i https://mirrors.aliyun.com/pypi/simple/
import requestsresp = requests.get(\"http://www.baidu.com\")print(resp.status_code)
requests.request 参数
表单
import requests# 1. 登录resp = requests.request( method=\'post\', url=\"http://api.fbi.com:9225/rest-v2/login/access_token\", data={ \"email\": \"bf@qq.com\", \"password\": \"bf123456\" })print(resp.status_code) # 人工判断if resp.status_code == 200: # 自动判断 print(\'成功\')else: print(\'失败\')
json
import requests# 1. 登录resp = requests.request( method=\'post\', url=\"http://api.fbi.com:9225/rest-v2/login/access_token\", json={ \"email\": \"bf@qq.com\", \"password\": \"bf123456\" })print(resp.status_code) # 人工判断if resp.status_code == 200: # 自动判断 print(\'成功\')else: print(\'失败\')
文件
import requests# 1. 登录resp = requests.request( method=\'post\', url=\"http://api.fbi.com:9225/rest-v2/login/access_token\", files={ \"email\": open(\"详细信息显示.mp4\", \"rb\"), \"password\": open(\"惺惺惜惺惺.mp4\", \"rb\"), })print(resp.status_code) # 人工判断if resp.status_code == 200: # 自动判断 print(\'成功\')else: print(\'失败\')
requests.Response
属性
状态码
响应头
resp = requests.request( method=\'get\', url=\"http://www.baidu.com\",)print(\"行:\", resp.status_code, resp.reason)print(\"头:\", resp.headers, )print(\"头:\", dict(resp.cookies), )
响应体
print(resp.content) # 二进制:传输print(resp.text) # 文本:阅读print(resp.json()) # 字典:使用 可能失败print(resp.json()[\'token_type\'])
requests.Session概念
cookies /session
(有多个请求构成的)同一个对话过程
共享参数
请求头(身份凭据)
共享网络
HTTP 协议基于 TCP,握手、挥手
最大端口 65535,其中 1024 保留
共享 cookies
🌰 示例场景:模拟用户登录知乎
假设我们使用requests.Session
模拟用户登录知乎,并在登录后访问个人主页:
- 第一次请求:访问登录页面(获取初始 Cookies)
- 第二次请求:提交登录表单(服务器验证并设置身份 Cookies)
- 第三次请求:访问个人主页(自动携带登录态 Cookies)
🚀 代码演示与解析
import requests# 创建Session对象(核心:自动管理Cookies)session = requests.Session()# 1. 第一次请求:访问登录页面(获取初始Cookies)response1 = session.get(\'https://www.zhihu.com/signin\')print(f\"第一次请求后Session中的Cookies: {session.cookies.get_dict()}\")# 输出示例: {\'_zap\': \'xxx\', \'d_c0\': \'yyy\'}
第一次请求详细过程:
- 客户端发送请求:
GET https://www.zhihu.com/signin
(无 Cookies) - 服务器响应:
- 返回登录页面 HTML
- 设置初始 Cookies(如
_zap=xxx; d_c0=yyy
)
Session
自动捕获并存储这些 Cookies- 此时
session.cookies
包含:{\'_zap\': \'xxx\', \'d_c0\': \'yyy\'}
- 此时
# 2. 第二次请求:提交登录表单(携带初始Cookies,获取身份Cookies)response2 = session.post( url=\'https://www.zhihu.com/api/v3/oauth/sign_in\', json={\'username\': \'test@example.com\', \'password\': \'123456\'})print(f\"第二次请求后Session中的Cookies: {session.cookies.get_dict()}\")# 输出示例: {\'_zap\': \'xxx\', \'d_c0\': \'yyy\', \'z_c0\': \'token123\'}
第二次请求详细过程:
- 客户端发送请求:
- 请求头自动携带第一次获取的 Cookies:
Cookie: _zap=xxx; d_c0=yyy
- 请求体:
{\"username\": \"test@example.com\", \"password\": \"123456\"}
- 请求头自动携带第一次获取的 Cookies:
- 服务器响应:
- 验证登录信息
- 设置身份 Cookies(如
z_c0=token123
,表示用户已登录)
Session
更新 Cookies:- 合并新 Cookies:
{\'_zap\': \'xxx\', \'d_c0\': \'yyy\', \'z_c0\': \'token123\'}
- 合并新 Cookies:
# 3. 第三次请求:访问个人主页(自动携带所有Cookies)response3 = session.get(\'https://www.zhihu.com/settings/profile\')print(f\"第三次请求后Session中的Cookies: {session.cookies.get_dict()}\")# 输出示例: {\'_zap\': \'xxx\', \'d_c0\': \'yyy\', \'z_c0\': \'token123\'}
第三次请求详细过程:
- 客户端发送请求:
- 请求头自动携带所有 Cookies:
Cookie: _zap=xxx; d_c0=yyy; z_c0=token123
- 请求头自动携带所有 Cookies:
- 服务器响应:
- 验证
z_c0
身份令牌 - 返回个人主页内容(只有登录用户可见)
- 验证
📊 三次请求对比表
/signin
_zap=xxx; d_c0=yyy
{\'_zap\': \'xxx\', \'d_c0\': \'yyy\'}
/api/v3/oauth/sign_in
_zap=xxx; d_c0=yyy
z_c0=token123
{\'_zap\': \'xxx\', \'d_c0\': \'yyy\', \'z_c0\': \'token123\'}
/settings/profile
_zap=xxx; d_c0=yyy; z_c0=token123
{\'_zap\': \'xxx\', \'d_c0\': \'yyy\', \'z_c0\': \'token123\'}
🔑 关键结论
- Cookies 自动传递:
Session
会自动在后续请求中携带之前获取的所有 Cookies,无需手动干预。 - Cookies 自动合并:如果服务器返回新的 Cookies(如登录后的身份令牌),
Session
会自动合并(覆盖同名 Cookies,保留不同名的)。 - 会话一致性:整个过程模拟了真实浏览器的行为,确保多个请求属于同一个用户会话。
🛠️ 调试技巧
如果你想查看每次请求的详细信息(包括请求头、响应头等),可以添加以下代码:
# 打印请求信息(包含Cookies)print(f\"请求URL: {response1.request.url}\")print(f\"请求头: {response1.request.headers}\")print(f\"响应状态码: {response1.status_code}\")print(f\"响应头: {response1.headers}\")
六、关联请求
接口关联:使用另一个接口的响应内容,作为下一个接口的请求参数
- 大部分接口都需要:共享参数
- 个别接口才需要:全局变量
其他的提取方式:re、jsonpath
jsonpath 用法:
- 针对字典
- 使用 jsonpath 语法
- 返回列表
import jsonpathtoken = jsonpath.jsonpath(resp.json(), \"$.access_token\")[0]s.headers.update({\"Authorization\": f\"bearer {token}\"})
jsonpath
讲解 🧐
jsonpath
是用于从 JSON 数据(Python 中常表现为字典、列表嵌套结构 )里提取特定内容的工具,类似字符串处理里的正则表达式,但专为 JSON 结构设计,在接口关联(从接口响应提取数据给下一个接口用)场景超实用,核心要点如下:
1. 基础作用 🎯
从复杂 JSON 结构里,按路径规则精准提取值。比如接口返回如下 JSON 数据(登录接口返回带 access_token
):
{ \"code\": 200, \"data\": { \"access_token\": \"abc123\", \"expires_in\": 3600 }, \"msg\": \"success\" }
想提取 access_token
,就可以用 jsonpath
按路径 $.data.access_token
快速拿到。
2. 语法规则(常用) 📖
$
$.data
→ 提取 data
字典内容:{\"access_token\": \"abc123\", \"expires_in\": 3600}
.
$.data.access_token
→ 提取 access_token
值:abc123
[]
$.data.users[0]
(假设 users
是列表,取第 1 个元素)- 过滤:
$.data.users[?(@.age>18)]
(取 users
里 age
大于 18 的对象)
3. 在代码里的使用步骤(结合接口关联场景) 💻
以登录后拿 token
给后续接口用为例:
import requests import jsonpath # 1. 发送登录接口请求,拿到响应 login_url = \"http://api.example.com/login\" login_data = {\"username\": \"test\", \"password\": \"123\"} resp = requests.post(login_url, json=login_data) # 2. 用 jsonpath 提取 access_token(假设响应是 JSON 格式) # resp.json() 把响应转成 Python 字典/列表,jsonpath 按规则提取 token_list = jsonpath.jsonpath(resp.json(), \"$.data.access_token\") # jsonpath 返回列表,提取第 1 个元素(实际场景需判断列表非空) token = token_list[0] if token_list else None # 3. 把 token 放到请求头,给后续接口用(比如带 Authorization) s = requests.Session() s.headers.update({\"Authorization\": f\"bearer {token}\"}) # 4. 后续接口请求自动带 token(实现接口关联) user_info_url = \"http://api.example.com/userinfo\" user_resp = s.get(user_info_url)
4. 常见场景 & 优势 🌟
- 接口关联必备:登录接口返回的
token
、创建订单返回的order_id
等,用jsonpath
能轻松提取,传给下一个接口当参数。 - 处理复杂结构:如果 JSON 嵌套很深(比如多层字典、列表混合),用
.
逐层找属性,比 Python 手动写循环 / 字典取值简洁太多。 - 兼容性:不管是
requests
响应转的字典,还是普通 JSON 格式字符串转的字典,都能处理,只要结构符合 JSON 规范。
5. 注意事项 ⚠️
jsonpath.jsonpath()
返回列表,如果没找到匹配内容,返回False
而不是空列表!所以实际用的时候,建议判断一下:-
result = jsonpath.jsonpath(data, \"$.xxx\") if result: value = result[0] else: # 处理提取失败逻辑,比如抛异常、设默认值 raise ValueError(\"提取 xxx 失败\")
- 语法别和 Python 字典取值搞混,
jsonpath
用$.data.access_token
,Python 原生是data[\"access_token\"]
,但jsonpath
更适合复杂结构遍历。
简单说,jsonpath
就是为 JSON 提取而生的 “导航仪”,按路径找数据超方便,接口自动化里处理响应、做关联请求必学~ 结合代码多试几个嵌套结构,就能快速掌握啦 ✨
七、完整的自动化实战
import requestsimport jsonpath# 0. 创建会话,共享参数和网络链接s = requests.Session()test_user = \"bf@qq.com\"test_pass = \"bf123456\"# 1. 登录resp = s.request( method=\'post\', url=\"http://api.fbi.com:9225/rest-v2/login/access_token\", json={ \"email\": test_user, \"password\": test_pass })if resp.status_code == 200: # 自动判断 print(\'登录成功\')else: print(\'登录失败\')# 共享参数(身份凭据)token = jsonpath.jsonpath(resp.json(), \"$.access_token\")[0]s.headers.update({\"Authorization\": f\"bearer {token}\"})# 2. 查询任务列表resp = s.request( \"get\", \"http://api.fbi.com:9225/rest-v2/todo\",)if resp.status_code == 200: print(\'查询任务列表成功\') total = resp.json()[\'total\'] print(f\"当前任务总数: {total}\")# 3. 创建任务resp = s.request( \"post\", \"http://api.fbi.com:9225/rest-v2/todo\", json={\"title\": \"新的任务\", \"is_done\": False})if resp.status_code == 200: print(\'创建任务成功\') # 提取变量,以便关联 new_id = jsonpath.jsonpath(resp.json(), \"$.id\")[0] print(f\"新创建的任务ID: {new_id}\")# 4. 再次查询任务列表resp = s.request( \"get\", \"http://api.fbi.com:9225/rest-v2/todo\",)if resp.status_code == 200: print(\'再次查询任务列表成功\') new_total = resp.json()[\'total\'] print(f\"当前任务总数: {new_total}\") if new_total == total + 1: print(\'任务创建验证成功\')# 5. 删除任务resp = s.request( \"delete\", f\"http://api.fbi.com:9225/rest-v2/todo/{new_id}\",)if resp.status_code == 200: print(\'删除任务成功\')# 6. 再次查询任务列表resp = s.request( \"get\", \"http://api.fbi.com:9225/rest-v2/todo\",)if resp.status_code == 200: print(\'最后一次查询任务列表成功\') renew_total = resp.json()[\'total\'] print(f\"当前任务总数: {renew_total}\") if renew_total == new_total - 1: print(\'任务删除验证成功\') if renew_total == total: print(\'任务总数恢复验证成功\')
在 API 认证中,Bearer
是一种常见的身份验证方案,用于在 HTTP 请求中传递令牌(Token),表明请求者的身份。它是 OAuth 2.0 协议的一部分,也是现代 API 最常用的认证方式之一。
📜 Bearer Token 的基本概念
Bearer
本质是一个授权类型(Authorization Type),告诉服务器:“我持有这个令牌,请验证我的身份”。它的格式通常是:
Authorization: Bearer
Bearer
:固定关键字,表示使用令牌认证。:服务器颁发的唯一身份令牌(如 JWT、Access Token)。
🔐 为什么需要 Bearer?
在传统的认证方式(如 Basic Auth)中,用户凭证(用户名 + 密码)会直接暴露在请求中,存在安全风险。而 Bearer Token
允许用户通过无状态、一次性的令牌访问受保护资源,避免了直接传递敏感信息。
常见场景:
- 用户登录后,服务器返回一个 Token(如 JWT)。
- 客户端在后续请求中携带这个 Token,无需再次登录。
- 服务器验证 Token 有效性,确认请求者身份。
🚀 代码中的 Bearer 实现
Bearer
用于传递登录后获取的 access_token
:
# 登录后提取Tokentoken = jsonpath.jsonpath(resp.json(), \"$.access_token\")[0]# 将Token添加到请求头,格式为 \"Bearer \"s.headers.update({\"Authorization\": f\"bearer {token}\"})
- 注意大小写:标准写法是
Bearer
(首字母大写),但有些服务器可能不区分大小写。
🔒 安全注意事项
-
Token 泄露风险:Bearer Token 相当于 “数字钥匙”,一旦泄露,攻击者可冒充用户访问系统。因此:
- 永远不要在 URL、日志或客户端代码中明文存储 Token。
- 优先使用 HTTPS 确保传输安全。
- 为 Token 设置合理的过期时间(如 2 小时)。
-
防止 CSRF 攻击:Bearer Token 通常用于 API 请求,需配合同源策略(Same-Origin Policy)或 CSRF 令牌保护。
🤔 Bearer vs. 其他认证方式
🌟 总结
Bearer
是一种通过令牌进行身份验证的机制,在现代 API 中广泛使用。它的核心是将服务器颁发的 Token 放在请求头中,格式为 Authorization: Bearer
,从而实现安全、无状态的身份验证。
total = resp.json()[\'total\']
是从 API 响应中提取数据的关键步骤!我来拆解一下它的含义和作用~ 🧐
1. 代码拆解:分步解释
① resp.json()
resp
是requests
发送请求后得到的响应对象。resp.json()
是将响应内容(通常是 JSON 格式的字符串)解析为 Python 字典或列表。
例如,如果响应是{\"code\": 200, \"total\": 10}
,则resp.json()
返回{\'code\': 200, \'total\': 10}
。
② [\'total\']
- 从解析后的字典中,通过键(Key)
\'total\'
提取对应的值。
例如,上面的例子中resp.json()[\'total\']
就是10
。
2. 结合场景理解
这行代码出现在 查询任务列表 的步骤中:
# 查询任务列表resp = s.request(\"get\", \"http://api.fbi.com:9225/rest-v2/todo\")if resp.status_code == 200: total = resp.json()[\'total\'] # 提取任务总数
假设 API 返回的 JSON 是这样的:
{ \"code\": 200, \"message\": \"success\", \"data\": { \"items\": [ {\"id\": 1, \"title\": \"任务1\"}, {\"id\": 2, \"title\": \"任务2\"} ], \"total\": 2 # 这是任务总数 }}
那么 resp.json()[\'total\']
就会提取出 2
,表示当前有 2 个任务。
3. 为什么需要这个值?
在自动化测试中,这个值通常用于 验证业务逻辑:
- 比如创建新任务后,检查总数是否增加 1。
- 删除任务后,检查总数是否减少 1。
# 创建任务前的总数total = resp.json()[\'total\'] # 创建任务...# 创建后的总数new_total = resp.json()[\'total\'] # 验证总数是否增加1if new_total == total + 1: print(\'任务创建验证成功\')
4. 潜在风险与优化
① Key 不存在的情况
如果 API 返回的 JSON 中没有 total
键,直接访问会报错。
优化建议:使用 .get()
方法,不存在时返回默认值(如 None
)。
total = resp.json().get(\'total\') # 不存在时返回Noneif total is not None: # 处理逻辑else: print(\"响应中缺少\'total\'字段\")
② 响应格式变化
如果 API 升级后返回格式变了(如 total
移到 data.total
),代码会失效。
优化建议:使用 jsonpath
更灵活地提取数据(已经在用啦!)。
total = jsonpath.jsonpath(resp.json(), \'$.total\')[0] # 更健壮的写法
🌟 总结
这行代码的核心作用是:从 API 响应中提取 任务总数,用于后续的业务逻辑验证(如创建 / 删除任务后检查数量变化)。它是接口自动化测试中 数据断言 的关键步骤!