Pytest接口自动化测试框架完整教程 - 从入门到熟悉

部署运行你感兴趣的模型镜像

📖 前言

大家好!今天给大家分享一个完整的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 生成了详尽的测试报告。


如果这篇文章对你有帮助,请点赞👍、收藏⭐、关注👆,你的支持是我创作的动力!

您可能感兴趣的与本文相关的镜像

Python3.10

Python3.10

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

web图解

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值