一、 接口自动化测试框架介绍
1、 接口测试场景
2、 自动化测试场景
3、 接口自动化测试与 Web/App 自动化测试区别
4、 接口测试工具类型
测试类型 |
工具 |
价值 |
接口抓包 |
Charles、Postman |
接口抓包工具,可以抓取 App 的数据包 |
接口测试 |
Postman |
接口调试工具,接口手工测试工具,学习成本低,直接安装即可使用 |
接口自动化测试 |
Requests、RestAssured |
用于接口自动化测试的 Java、Python 第三方库,需要与对应编程语言结合使用 |
性能测试 |
JMeter |
性能测试工具 |
5、 Requests
6、 Requests 优势
- 功能全面:HTTP/HTTPS 支持全面。
- 使用简单:简单易用,不用关心底层细节。
- 定制性高:结合测试框架完成二次封装,比如 HttpRunner。
7、 Requests 环境准备
- 安装命令:
pip install requests
二、接口请求方法
1、 常见 HTTP 请求方法构造
方法 |
说明 |
requests.request() |
构造一个请求,支撑以下各方法的基础方法。 |
requests.get() |
构造 HTTP 协议中的 GET 请求。 |
requests.post() |
构造 HTTP 协议中的 POST 请求。 |
requests.put() |
构造 HTTP 协议中的 PUT 请求。 |
requests.delete() |
构造 HTTP 协议中的 DELETE 请求。 |
2、 底层设计
3、 HTTP 协议知识
3、 构造 GET 请求
-
requests.get(url, params=None, **kwargs)
- url: 接口 url。
- params:拼接在 url 中的请求参数,非必填,不填默认为None。
- **kwargs:更多底层支持的参数。
# 导入依赖
import requests
def test_get():
# 定义接口的 url 和拼接在 url 中的请求参数
url = "https://httpbin.ceshiren.com/get"
# 发出 GET 请求,r 接收接口响应
r = requests.get(url)
# 打印接口响应
logger.info(f"接口响应为 {r}")
4、 构造 POST 请求
-
requests.post(url, data=None, json=None, **kwargs)
- url: 接口 url。
- data:表单格式请求体。
- json:JSON 格式请求体。
- **kwargs:更多底层支持的参数。
# 导入依赖
import requests
def test_post():
# 定义接口的 url
url = "https://httpbin.ceshiren.com/post"
# 发出 POST 请求,r 接收接口响应
r = requests.post(url)
# 打印接口响应
logger.info(f"接口响应为 {r}")
5、 构造 PUT 请求
-
requests.put(url, data=None, **kwargs)
- url: 接口 url。
- data:表单格式请求体。
- **kwargs:更多底层支持的参数。
# 导入依赖
import requests
def test_put():
# 定义接口的 url
url = "https://httpbin.ceshiren.com/put"
# 发出 POST 请求,r 接收接口响应
r = requests.put(url)
# 打印接口响应
logger.info(f"接口响应为 {r}")
6、 构造 DELETE 请求
-
requests.delete(url, **kwargs)
- url: 接口 url。
- **kwargs:更多底层支持的参数。
# 导入依赖
import requests
def test_delete():
# 定义接口的 url 和表单格式请求体
url = "https://httpbin.ceshiren.com/delete"
# 发出 POST 请求,r 接收接口响应
r = requests.delete(url)
# 打印接口响应
logger.info(f"接口响应为 {r}")
实战代码
import requests
# 发起get请求
def test_res_get():
url = "https://httpbin.ceshiren.com/get"
# 发起get请求, 并返回一个响应对象
r = requests.get(url)
# 把响应对象以json的形式输出
print(r.json())
# 发起post请求
def test_res_post():
url = "https://httpbin.ceshiren.com/post"
# 发起get请求, 并返回一个响应对象
r = requests.post(url)
# 打印文本格式的响应信息,如果出现method not allowed,代表请求方法使用错误
print(r.text)
# 发起put请求
def test_res_put():
url = "https://httpbin.ceshiren.com/put"
# 发起get请求, 并返回一个响应对象
r = requests.put(url)
# 打印文本格式的响应信息,如果出现method not allowed,代表请求方法使用错误
print(r.text)
# 发起delete请求
def test_res_delete():
url = "https://httpbin.ceshiren.com/delete"
# 发起get请求, 并返回一个响应对象
r = requests.delete(url)
# 打印文本格式的响应信息,如果出现method not allowed,代表请求方法使用错误
print(r.text)
7、 构造请求方法
requests.request(method, url, **kwargs)
- method: 请求方法。
-
GET
,OPTIONS
,HEAD
,POST
,PUT
,PATCH
,DELETE
。
- url: 接口 url。
- **kwargs:更多底层支持的参
8、 底层参数说明
参数 |
应用场景 |
method |
请求方法 |
url |
请求 URL |
params |
请求中携带 URL 参数 |
data |
请求中携带请求体(默认为表单请求) |
json |
请求中携带 json 格式的请求体 |
headers |
请求中携带头信息 |
cookies |
请求中携带 cookies |
files |
请求中携带文件格式的请求体 |
auth |
请求中携带认证信息 |
timeout |
设置请求超时时间 |
allow_redirects |
请求是否允许重定向 |
proxies |
设置请求代理 |
verify |
请求是否要认证 |
cert |
请求中携带 ssl 证书 |
三、接口请求参数
1、 请求参数简介
- 接口请求中携带的,会在路径之后使用
?
代表客户端向服务端传递的参数。
- 使用
key=value
形式拼接在 URL 中。
- 如果有多个,则使用
&
分隔
- 例如:
2、 携带请求参数的方式
- 常用两种方式:
- 直接在 URL 中拼接:
?username=Hogwarts&id=666
。
- 通过 params 参数传递:
requests.get(url, params)
3、 携带请求参数的 GET 请求
import requests
# 发起get请求
def test_get_url():
# url中携带参数信息
url = "https://httpbin.ceshiren.com/get?name=ad&age=18"
r = requests.get(url)
print(r.text)
def test_get_params():
url = "https://httpbin.ceshiren.com/get"
# 定义一个字典格式的变量信息
req_params = {"name": "ad", "age": 18}
# 通过params进行传参
r = requests.get(url, params=req_params)
print(r.text)
4、 携带请求参数的 POST 请求
import requests
# ===其他类型的请求也可以携带url参数信息
# 发起get请求
def test_post_url():
# url中携带参数信息
url = "https://httpbin.ceshiren.com/post?name=ad1&age=19"
r = requests.post(url)
print(r.text)
def test_post_params():
# url不携带参数信息
url = "https://httpbin.ceshiren.com/post"
# 定义一个字典格式的变量信息
req_params = {"name": "ad1", "age": 19}
# 通过params进行传参
r = requests.post(url, params=req_params)
print(r.text)
四、接口请求头
1、 请求头信息的使用场景
例如:飞书接口文档
2、 请求头信息
- HTTP 请求头是在 HTTP 请求消息中包含的元数据信息,用于描述请求或响应的一些属性和特征。
-
实际工作过程中具体要关注的头信息字段需要和研发沟通。
- 常见的头信息(下面表格):
内容 |
含义 |
Authorization |
表示客户端请求的身份验证信息 |
Cookie |
表示客户端的状态信息,通常用于身份验证和会话管理 |
Content-Type |
表示请求消息体的 MIME 类型 |
User-Agent |
发送请求的客户端软件信息 |
3、 构造头信息
- 使用 headers 参数传入。
- 通常使用字典格式。
headers = {'user-agent': 'my-app/0.0.1'}
r = requests.get(url, headers=headers)
import requests
def test_res_get():
url = "https://httpbin.ceshiren.com/get"
# 通过字典格式定义一个头信息
header = {"name": "ad", "User-Agent": "apple", "Content-type": "application/json"}
r = requests.get(url, headers=header)
print(r.text)
五、接口请求体-json
1、 接口请求体简介
2、常用接口请求体
类型 |
介绍 |
Content-type |
JSON(JavaScript Object Notation) |
轻量级的数据交换格式,最常见的一种类型。 |
application/json |
表单数据(Form Data) |
以键值对的形式提交数据,例如通过 HTML 表单提交数据。 |
application/x-www-form-urlencoded |
XML(eXtensible Markup Language) |
常用的标记语言,通常用于传递配置文件等数据。 |
application/xml |
text/xml |
|
|
文件(File) |
可以通过请求体上传文件数据,例如上传图片、视频等文件。 |
上传文件的 MIME 类型,例如 image/jpeg |
multipart/form-data |
|
|
纯文本(Text) |
纯文本数据,例如发送邮件、发送短信等场景 |
text/plain |
其他格式 |
二进制数据、protobuf 等格式 |
|
3、 JSON 简介
- JavaScript Object Notation 的缩写。
- 是一种轻量级的数据交换格式。
- 是理想的接口数据交换语言。
- Content-Type 为 application/json。
4、构造 JSON 格式请求体
import requests
def test_body_json():
# httpbin.ceshiren.com是请求什么返回什么
url = "https://httpbin.ceshiren.com/post"
# 定义变量,存放请求体
req_body = {
"name": "holy",
"age": 19
}
# 通过json关键字传递请求体信息
r = requests.post(url, json=req_body)
print(r.text)
六、接口响应断言
1、 接口断言使用场景
- 问题:
- 如何确保请求可以发送成功。
- 如何保证符合业务需求。
- 解决方案:
- 通过获取响应信息,验证接口请求是否成功,是否符合业务需求。
2、 Requests 中的响应结果对象
import requests
from requests import Response
# Response就是一个响应对象
r: Response = requests.get('http://www.example.com')
3、 响应结果类型
属性 |
含义 |
r |
响应 Response 对象(可以使用任意的变量名) |
r.status_code |
HTTP 响应状态码 |
r.headers |
返回一个字典,包含响应头的所有信息。 |
r.text |
返回响应的内容,是一个字符串。 |
r.url |
编码之后的请求的 url |
r.content |
返回响应的内容,是一个字节流。 |
r.raw |
响应的原始内容 |
r.json() |
如果响应的内容是 JSON 格式,可以使用该方法将其解析成 Python 对象。 |
4、 响应状态码断言
import requests
def test_res():
url = "https://httpbin.ceshiren.com/get"
# 返回值为一个Response对象
r = requests.get(url)
# 断言,确定响应状态码为200
assert r.status_code == 200
# 断言url
assert r.url == "https://httpbin.ceshiren.com/get"
# 打印响应状态码信息
print(r.status_code)
print("-----------")
# 打印响应头信息
print(r.headers)
print("-----------")
# 打印响应体信息
print(r.text)
print("-----------")
# 打印编码之后的请求的 url
print(r.url)
七、json响应体断言
1、 JSON 响应体
- JSON格式的响应体指的是HTTP响应中的消息体(message body),它是以JSON格式编码的数据。
{
"name": "John",
"age": 30,
"city": "New York"
}
2、断言 JSON 格式响应体使用场景
3、 断言 JSON 格式响应体
4、 若碰到复杂断言应该如何处理?
- 多层嵌套的数据提取与断言: JSONPath
- 整体结构响应断言: JSONSchema
- 自行编写解析算法
import requests
def test_res_json():
url = "https://httpbin.ceshiren.com/get"
# 如果响应体是非json的场景,就不能使用r.json()的方式,否则会报raise RequestsJSONDecodeError异常,可以使用r.text
r = requests.get(url)
print(r.json())
# 断言响应状态码。只能判断请求是否成功发送
assert r.status_code == 200
# r.json()将json格式转换为字典,断言外层的某个字段数据,和业务场景相关
assert r.json()["url"] == url
# 断言多层的场景
assert r.json()["headers"]["Host"] == "httpbin.ceshiren.com"
八、宠物商店接口自动化测试实战
1、实战思路
2、自动化测试脚本思路
"""
完成宠物商城宠物查询功能接口自动化测试。
编写自动化测试脚本。
完成断言。
"""
import requests
class TestPetStoreSearch:
"""
宠物商店查询单接口测试
"""
def setup_class(self):
# 定义请求url
self.base_url = "https://petstore.swagger.io/v2/pet"
# 拼接宠物查询接口url
self.search_url = self.base_url + "/findByStatus"
def test_search_pet(self):
# 宠物查询接口请求参数
pet_status = {
"status": "available"
}
# 发出请求
r = requests.get(self.search_url, params=pet_status)
# 打印接口响应信息
print(r.text)
# 状态断言
assert r.status_code == 200
# 业务断言,r.json()转换为python对象,不是空列表
assert r.json() != []
# 响应返回的是一个数组,数组中每一个对象都应该包含id字段
assert "id" in r.json()[0]
日志配置: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)
代码优化
- 新建日志配置。
- 在用例中使用配置好的日志实例。
- 使用 pytest parametrize 装饰器实现宠物状态的参数化。
- 测试报告添加标题
"""
完成宠物商城宠物查询功能接口自动化测试。
编写自动化测试脚本。
完成断言。
"""
import allure
import pytest
import requests
from interface.utils.log_utils import logger
@allure.feature("宠物查询接口")
class TestPetStoreSearch:
"""
宠物商店查询单接口测试
"""
def setup_class(self):
# 定义请求url
self.base_url = "https://petstore.swagger.io/v2/pet"
# 拼接宠物查询接口url
self.search_url = self.base_url + "/findByStatus"
# 正常场景用例
# 参数化,第一个为需要传的参数,第二个为传的值,可以使用列表或元组,ids给用例命名
@pytest.mark.parametrize(
"status",
["available", "pending", "sold"],
ids=["available_pets", "pending_pets", "sold_pets"]
)
@allure.story("宠物查询接口冒烟用例")
def test_search_pet(self, status):
# 宠物查询接口请求参数
pet_status = {
"status": status
}
# 发出请求
r = requests.get(self.search_url, params=pet_status)
# 添加日志,打印接口响应信息
logger.info(r.text)
# 状态断言
assert r.status_code == 200
# 业务断言,r.json()转换为python对象,不是空列表
assert r.json() != []
# 响应返回的是一个数组,数组中每一个对象都应该包含id字段
assert "id" in r.json()[0]
# 异常场景,参数化
@pytest.mark.parametrize(
"status",
["petstatus", 123456, ""],
ids=["wrong_value", "number", "none_str"]
)
@allure.story("status传入错误的值")
def test_search_abnormal(self, status):
"""
宠物查询接口异常场景
:return:
"""
# 宠物查询接口请求参数
pet_status = {
"status": status
}
# 发出请求
r = requests.get(self.search_url, params=pet_status)
# 添加日志,打印接口响应信息
logger.info(r.text)
# 状态断言
assert r.status_code == 200
# 业务断言,r.json()转换为python对象,不是空列表
assert r.json() == []
# 不传参
@allure.story("不传status")
def test_search_none_params(self):
# 发出请求
r = requests.get(self.search_url)
# 添加日志,打印接口响应信息
logger.info(r.text)
# 状态断言
assert r.status_code == 200
# 业务断言,r.json()转换为python对象,不是空列表
assert r.json() == []
# 传非status的参数
@allure.story("传入非status参数")
def test_search_wrong_params(self):
pet_status = {
"key": "available "
}
# 发出请求
r = requests.get(self.search_url, params=pet_status)
# 添加日志,打印接口响应信息
logger.info(r.text)
# 状态断言
assert r.status_code == 200
# 业务断言,r.json()转换为python对象,不是空列表
assert r.json() == []
生成测试报告
# 生成报告信息
pytest -vs test_petsearch_l1.py --alluredir=./reports/
# 生成报告在线服务,查看报告
allure serve ./reports/