📖 前言
大家好!今天给大家分享一个完整的Pytest接口自动化测试框架教程。这个教程基于一个真实的博客系统接口测试项目,从零开始教你如何使用Pytest进行接口自动化测试。
为什么选择Pytest?
- 🚀 简单易学,语法简洁
- 🔧 功能强大,插件丰富
- 📊 报告美观,支持Allure
- 🎯 社区活跃,文档完善
🛠 环境准备
1. Python环境安装
确保您的系统已安装Python 3.7+版本。
# 检查Python版本
python --version
# 或
python3 --version
2. 项目依赖安装
创建项目目录并安装必要的依赖包:
# 安装依赖包
pip install pytest==8.3.2
pip install allure-pytest==2.13.5
pip install jsonschema==4.23.0
pip install PyYAML==6.0.1
pip install requests==2.31.0
3. 项目结构搭建
pytest_tutorial/
├── cases/ # 测试用例目录
│ ├── __init__.py
│ ├── test_login.py # 登录测试
│ ├── test_add.py # 添加博客测试
│ ├── test_list.py # 博客列表测试
│ ├── test_detail.py # 博客详情测试
│ ├── test_getUserInfo.py # 用户信息测试
│ └── test_getAuthorInfo.py # 作者信息测试
├── utils/ # 工具类目录
│ ├── __init__.py
│ ├── logger_util.py # 日志工具
│ ├── request_util.py # 请求工具
│ └── yaml_util.py # YAML工具
├── data/ # 测试数据目录
│ └── data.yml # 测试数据文件
├── logs/ # 日志文件目录
├── allure-results/ # Allure测试结果
├── allure-reports/ # Allure测试报告
├── pytest.ini # pytest配置文件
├── requirements.txt # 依赖包文件
└── run.py # 运行脚本
🏗 核心组件详解
1. 日志工具类 (logger_util.py)
日志是调试和问题定位的重要工具,我们先创建一个完善的日志系统:
import logging
import os.path
import time
class infoFilter(logging.Filter):
"""信息日志过滤器"""
def filter(self, record):
return record.levelno == logging.INFO
class errFilter(logging.Filter):
"""错误日志过滤器"""
def filter(self, record):
return record.levelno == logging.ERROR
class logger:
"""日志工具类"""
@classmethod
def getlog(cls):
# 创建日志对象
cls.logger = logging.getLogger(__name__)
cls.logger.setLevel(logging.DEBUG)
# 获取项目根目录
LOG_PATH = "./logs/"
if not os.path.exists(LOG_PATH):
os.mkdir(LOG_PATH)
# 按日期创建日志文件名
now = time.strftime("%Y-%m-%d")
log_name = LOG_PATH + now + ".log" # 所有日志
info_log_name = LOG_PATH + now + "-info.log" # 信息日志
err_log_name = LOG_PATH + now + "-err.log" # 错误日志
# 创建文件处理器
all_handler = logging.FileHandler(log_name, encoding="utf-8")
info_handler = logging.FileHandler(info_log_name, encoding="utf-8")
err_handler = logging.FileHandler(err_log_name, encoding="utf-8")
# 创建控制台处理器
streamHandler = logging.StreamHandler()
# 设置日志格式
formatter = logging.Formatter(
"%(asctime)s %(levelname)s [%(name)s] [%(filename)s (%(funcName)s:%(lineno)d)] - %(message)s"
)
# 设置处理器格式
all_handler.setFormatter(formatter)
info_handler.setFormatter(formatter)
err_handler.setFormatter(formatter)
streamHandler.setFormatter(formatter)
# 添加过滤器
info_handler.addFilter(infoFilter())
err_handler.addFilter(errFilter())
# 添加处理器到日志器
cls.logger.addHandler(all_handler)
cls.logger.addHandler(info_handler)
cls.logger.addHandler(err_handler)
# cls.logger.addHandler(streamHandler) # 可选:控制台输出
return cls.logger
日志系统特点:
- 按日期自动创建日志文件
- 分类记录(全部/信息/错误)
- 统一的日志格式
- 详细的调用信息(文件名、函数名、行号)
2. HTTP请求工具类 (request_util.py)
封装HTTP请求,统一接口调用方式:
import requests
from utils.logger_util import logger
# 测试服务器地址
host = "http://8.137.19.140:9090/"
class Request:
"""HTTP请求工具类"""
log = logger.getlog()
def get(self, url, **kwargs):
"""发送GET请求"""
self.log.info("准备发起get请求,url:" + url)
self.log.info("接口信息:{}".format(kwargs))
r = requests.get(url=url, **kwargs)
self.log.info("接口响应状态码:{}".format(r.status_code))
self.log.info("接口响应内容:{}".format(r.text))
return r
def post(self, url, **kwargs):
"""发送POST请求"""
self.log.info("准备发起post请求,url:" + url)
self.log.info("接口信息:{}".format(kwargs))
r = requests.post(url=url, **kwargs)
self.log.info("接口响应状态码:{}".format(r.status_code))
self.log.info("接口响应内容:{}".format(r.text))
return r
请求工具特点:
- 统一的HTTP请求封装
- 自动记录请求和响应日志
- 支持所有requests参数
- 简化的API调用方式
3. YAML数据工具类 (yaml_util.py)
管理测试数据,实现数据持久化:
import os
import yaml
def write_yaml(filename, data):
"""往yml中写数据"""
with open(os.getcwd() + "/data/" + filename, mode="a+", encoding="utf-8") as f:
yaml.safe_dump(data, stream=f)
def read_yaml(filename, key):
"""读取yml中数据"""
with open(os.getcwd() + "/data/" + filename, mode="r", encoding="utf-8") as f:
data = yaml.safe_load(f)
return data[key]
def clear_yaml(filename):
"""清空yml中数据"""
with open(os.getcwd() + "/data/" + filename, mode="w", encoding="utf-8") as f:
f.truncate()
数据工具特点:
- 数据持久化存储
- 测试数据共享
- 数据清理功能
- 统一的数据管理
📝 测试用例编写实战
1. 登录测试用例 (test_login.py)
让我们从最基础的登录测试开始:
import re
import pytest
from jsonschema.validators import validate
from utils.request_util import host, Request
from utils.yaml_util import write_yaml
@pytest.mark.order(1) # 设置执行顺序
class TestLogin:
"""登录测试类"""
url = host + "user/login"
# JSON Schema 定义 - 验证响应格式
schema = {
"type": "object",
"required": ["code", "errMsg", "data"],
"additionalProperties": False,
"properties": {
"code": {"type": "string"},
"errMsg": {"type": "string"},
"data": {"type": ["string", "null"]}
}
}
# 参数化测试 - 异常登录场景
@pytest.mark.parametrize("login", [
{
"username": "zhang",
"password": "123",
"errMsg": '用户不存在'
},
{
"username": "zhangsan",
"password": "123",
"errMsg": '密码错误'
},
{
"username": "",
"password": "",
"errMsg": '账号或密码不能为空'
}
])
def test_login_fail(self, login):
"""测试登录失败场景"""
data = {
"username": login["username"],
"password": login["password"]
}
r = Request().post(url=self.url, data=data)
# JSON Schema 验证
validate(instance=r.json(), schema=self.schema)
# 断言验证
assert r.json()["code"] == "FAIL"
assert r.json()["errMsg"] == login["errMsg"]
# 参数化测试 - 正常登录场景
@pytest.mark.parametrize("login", [
{
"username": "zhangsan",
"password": "123456",
},
{
"username": "lisi",
"password": "123456",
}
])
def test_login_success(self, login):
"""测试登录成功场景"""
data = {
"username": login["username"],
"password": login["password"]
}
r = Request().post(url=self.url, data=data)
validate(instance=r.json(), schema=self.schema)
assert r.json()["code"] == "SUCCESS"
assert re.match('\S{100,}', r.json()['data']) # 验证token格式
# 保存token到YAML文件,供其他测试使用
token = {
"user_token_header": r.json()['data']
}
write_yaml("data.yml", token)
关键知识点解析:
🎯 pytest.mark.order() - 测试执行顺序
@pytest.mark.order(1) # 设置测试执行顺序
- 确保登录测试在其他测试之前执行
- 需要安装
pytest-order插件:pip install pytest-order
📊 @pytest.mark.parametrize() - 参数化测试
@pytest.mark.parametrize("login", [
{"username": "zhang", "password": "123", "errMsg": '用户不存在'},
{"username": "zhangsan", "password": "123", "errMsg": '密码错误'}
])
def test_login_fail(self, login):
# 测试逻辑
- 数据驱动测试:一个测试方法执行多个测试场景
- 减少代码重复:避免为每个场景写单独的测试方法
- 提高测试覆盖率:轻松添加新的测试数据
🔍 JSON Schema 验证
schema = {
"type": "object",
"required": ["code", "errMsg", "data"],
"properties": {
"code": {"type": "string"},
"errMsg": {"type": "string"},
"data": {"type": ["string", "null"]}
}
}
validate(instance=r.json(), schema=self.schema)
- 验证响应数据结构:确保API返回格式正确
- 提前发现数据结构问题:避免运行时错误
- 提高测试稳定性:数据结构变化时及时发现问题
2. 博客添加测试 (test_add.py)
import pytest
from jsonschema.validators import validate
from utils.request_util import host, Request
from utils.yaml_util import read_yaml
class TestAdd:
"""博客添加测试类"""
url = host + "blog/add"
schema = {
"type": "object",
"properties": {
"code": {"type": "string"},
"errMsg": {"type": "string"},
"data": {"type": "boolean"}
},
"additionalProperties": False,
"required": ["code", "errMsg", "data"]
}
def test_add_noLogin(self):
"""测试未登录状态"""
r = Request().post(url=self.url)
assert r.status_code == 401
@pytest.mark.parametrize("add", [
{
"title": "接口自动化标题",
"content": "接口自动化内容",
"data": True
},
{
"title": "",
"content": "接口自动化内容",
"data": False
},
{
"title": "接口自动化标题",
"content": "",
"data": False
}
])
def test_add(self, add):
"""测试博客添加功能"""
# 从YAML文件读取token
token = read_yaml("data.yml", "user_token_header")
header = {
"user_token_header": token
}
json_data = {
"title": add["title"],
"content": add["content"]
}
r = Request().post(url=self.url, json=json_data, headers=header)
validate(instance=r.json(), schema=self.schema)
assert r.json()['data'] == add['data']
关键知识点:
🔐 Token 认证机制
token = read_yaml("data.yml", "user_token_header")
header = {
"user_token_header": token
}
r = Request().post(url=self.url, json=json_data, headers=header)
- 从YAML文件读取登录token
- 在请求头中携带认证信息
- 实现测试用例间的数据共享
3. 博客列表测试 (test_list.py)
import pytest
from jsonschema.validators import validate
from utils.request_util import host, Request
from utils.yaml_util import read_yaml, write_yaml
@pytest.mark.order(2) # 在登录后执行
class TestList:
"""博客列表测试类"""
url = host + "blog/getList"
schema = {
"type": "object",
"properties": {
"code": {"type": "string"},
"errMsg": {"type": "string"},
"data": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"id": {"type": "integer"},
"title": {"type": "string"},
"content": {"type": "string"},
"userId": {"type": "integer"},
"deleteFlag": {"type": "integer"},
"createTime": {"type": "string"},
"updateTime": {"type": "string"},
"loginUser": {"type": "boolean"}
},
"required": ["id", "title", "content", "userId", "deleteFlag", "createTime", "updateTime", "loginUser"]
}
}
},
"required": ["code", "errMsg", "data"]
}
def test_list_noLogin(self):
"""测试未登录状态"""
r = Request().get(url=self.url)
assert r.status_code == 401
def test_list_login(self):
"""测试登录状态下的列表获取"""
token = read_yaml("data.yml", "user_token_header")
header = {
"user_token_header": token
}
r = Request().get(url=self.url, headers=header)
validate(instance=r.json(), schema=self.schema)
assert r.json()['code'] == "SUCCESS"
# 保存博客ID供后续测试使用
blogId = {
"blogId": r.json()['data'][0]['id']
}
write_yaml("data.yml", blogId)
关键知识点:
🔗 测试用例间的数据传递
# 在test_list_login中保存blogId
blogId = {
"blogId": r.json()['data'][0]['id']
}
write_yaml("data.yml", blogId)
# 在其他测试中使用blogId
blogId = read_yaml("data.yml", "blogId")
url = self.url + "?blogId=" + str(blogId)
- 通过YAML文件实现测试数据共享
- 模拟真实的业务流程
- 提高测试的连贯性
🚀 运行测试
1. pytest配置文件 (pytest.ini)
[pytest]
addopts = -vs --alluredir allure-results
配置说明:
addopts: 默认命令行选项-v: 详细输出模式-s: 不捕获输出(显示print语句)--alluredir allure-results: 指定Allure结果目录
2. 基本运行命令
# 运行所有测试
pytest
# 运行指定测试文件
pytest cases/test_login.py
# 运行指定测试类
pytest cases/test_login.py::TestLogin
# 运行指定测试方法
pytest cases/test_login.py::TestLogin::test_login_success
# 详细输出
pytest -v
# 显示print输出
pytest -s
# 组合使用
pytest -vs
# 运行指定标记的测试
pytest -m "order"
# 生成HTML报告
pytest --html=report.html
# 并行运行(需要安装pytest-xdist)
pytest -n 4
📊 测试报告
1. Allure 测试报告
Allure是一个强大的测试报告框架,提供美观的测试结果展示。
安装Allure
# 安装Allure命令行工具
# Windows
scoop install allure
# 或下载安装包:https://github.com/allure-framework/allure2/releases
# Mac
brew install allure
# Linux
sudo apt-get install allure
生成报告
# 运行测试并生成Allure结果
pytest
# 生成Allure报告
allure generate allure-results -o allure-reports --clean
# 打开报告
allure open allure-reports
报告特性
- 测试执行统计:通过率、失败率、跳过率
- 测试用例详情:每个测试用例的执行情况
- 失败用例分析:详细的错误信息和堆栈跟踪
- 测试步骤记录:每个测试步骤的执行时间
2. 日志文件
测试执行过程中会生成详细的日志文件:
logs/
├── 2025-01-15.log # 所有日志
├── 2025-01-15-info.log # 信息日志
└── 2025-01-15-err.log # 错误日志
日志格式示例:
2025-01-15 10:30:15,123 INFO [__main__] [test_login.py (test_login_success:85)] - 准备发起post请求,url:http://8.137.19.140:9090/user/login
2025-01-15 10:30:15,456 INFO [__main__] [test_login.py (test_login_success:85)] - 接口信息:{'data': {'username': 'zhangsan', 'password': '123456'}}
2025-01-15 10:30:15,789 INFO [__main__] [test_login.py (test_login_success:85)] - 接口响应状态码:200
2025-01-15 10:30:15,790 INFO [__main__] [test_login.py (test_login_success:85)] - 接口响应内容:{"code":"SUCCESS","errMsg":"","data":"eyJhbGciOiJIUzI1NiJ9..."}
❓ 常见问题与解决方法
1. 测试执行顺序问题
问题: 测试用例执行顺序不确定
# 解决方案:使用pytest-order插件
@pytest.mark.order(1)
def test_login(self):
pass
@pytest.mark.order(2)
def test_add_blog(self):
pass
2. 数据依赖问题
问题: 测试用例间数据依赖
# 解决方案:使用YAML文件共享数据
# 在test_login中保存token
write_yaml("data.yml", {"token": response_data})
# 在其他测试中读取token
token = read_yaml("data.yml", "token")
3. 测试数据管理
问题: 测试数据混乱
# 解决方案:数据分类管理
test_data = {
"valid_users": [
{"username": "zhangsan", "password": "123456"},
{"username": "lisi", "password": "123456"}
],
"invalid_users": [
{"username": "", "password": ""},
{"username": "invalid", "password": "wrong"}
]
}
4. 日志过多问题
问题: 日志文件过大
# 解决方案:日志轮转
import logging.handlers
handler = logging.handlers.RotatingFileHandler(
'test.log', maxBytes=10*1024*1024, backupCount=5
)
🎉 总结
通过本教程,我们掌握了 Pytest 接口自动化测试的核心内容,包括测试用例编写、参数化测试和断言方法,以及接口请求封装、JSON Schema 验证等关键技能。在测试数据管理方面,学习了使用 YAML 进行数据存储与共享,并构建了分类日志系统以提升问题定位效率,同时结合 Allure 生成了详尽的测试报告。
如果这篇文章对你有帮助,请点赞👍、收藏⭐、关注👆,你的支持是我创作的动力!
964

被折叠的 条评论
为什么被折叠?



