pytest__完整教学流程(1.0)

Pytest全维度实战教学文档-目录大纲

前言

Pytest框架定位、核心优势、文档适用场景

一、环境搭建与基础认知

1.1 环境安装与版本校验(核心插件批量安装)
1.2 Pytest核心命名识别规则(重点)
1.3 标准测试用例编写(行业AAA实战模式)

二、Pytest核心基础语法

2.1 原生断言机制(核心优势、精准报错)
2.2 常用断言场景全覆盖案例
2.3 标准化异常断言写法
2.4 测试用例分层管理(函数/类/模块)
2.5 用例跳过、预期失败用法

三、Pytest高阶核心特性(自动化核心)

3.1 Fixture夹具详解(框架灵魂功能)
3.2 Fixture基础前置后置用法
3.3 Fixture四大作用域详解与实战
3.4 Fixture嵌套依赖与参数传递
3.5 批量参数化测试(高效造用例)
3.6 参数化别名、用例标记实战

四、企业级全场景实战案例

4.1 业务函数单元测试(全覆盖场景)
4.2 HTTP接口自动化测试(主流业务场景)
4.3 Pytest-mock数据依赖隔离测试

五、工程化执行与测试报告

5.1 可视化HTML测试报告生成
5.2 代码覆盖率统计与分析
5.3 分布式并行执行(大幅提升用例执行效率)
5.4 用例精准筛选、重跑、过滤规则

六、企业级统一配置管理

6.1 pytest.ini核心配置文件详解
6.2 团队统一规则、标记、日志、重试配置

七、高频问题踩坑与解决方案

7.1 用例无法识别、不执行问题排查
7.2 Fixture数据污染、用例互相影响解决
7.3 参数化用例报错定位不准优化方案
7.4 并行执行资源冲突报错解决方案

八、企业标准化最佳实践规范

8.1 用例分层、夹具分层规范
8.2 用例独立性、数据隔离规范
8.3 断言规范、命名规范、编写准则

九、高阶插件生态拓展

9.1 主流实战插件功能适配场景
9.2 报告、Web、UI、异步测试插件适配
 
===================================================
Pytest 全维度实战教学文档(资深测试工程师专属)

前言

本文档面向软件测试工程师,摒弃基础入门冗余内容,聚焦 Pytest 核心原理、工程化用法、实战场景、踩坑解决方案,覆盖单元测试、接口测试、自动化测试全场景,配套完整可运行案例、企业级编码规范、调试技巧及插件生态,可作为团队培训文档、日常开发手册、面试复习资料。
相较于 Python 内置 unittest 框架,Pytest 具备语法简洁、无需类与方法束缚、Fixture 灵活前置后置、参数化强大、断言原生、插件生态丰富六大核心优势,是目前 Python 自动化测试的行业标准框架。

一、环境搭建与基础认知

1.1 环境安装与版本校验

支持 Python3.7+ 全版本,安装命令简洁,同时推荐配套常用插件,适配企业自动化测试场景。
# 安装最新版pytest
pip install -U pytest

# 企业必备配套插件(一次性安装)
pip install pytest-cov pytest-html pytest-xdist pytest-order pytest-mock

# 版本校验
pytest --version
插件说明:
  • pytest-cov:代码覆盖率统计
  • pytest-html:生成可视化测试报告
  • pytest-xdist:分布式并行执行用例
  • pytest-order:控制用例执行顺序
  • pytest-mock:模拟数据/接口/函数

1.2 Pytest 核心命名规则(重点)

Pytest 基于命名规则自动识别测试用例,无需手动注册,严格遵守以下规则方可自动执行:
  • 测试文件:以 test_*.py 开头 或 *_test.py 结尾
  • 测试函数:独立函数以 test_ 开头
  • 测试类:以 Test 开头(禁止带 __init__ 构造方法)
  • 测试类方法:以 test_ 开头

1.3 第一个标准测试用例(AAA模式)

行业通用AAA测试模式(Arrange准备、Act执行、Assert断言),所有企业级用例均需遵循该结构,可读性、可维护性极强。
步骤1:编写被测代码 calculator.py
def add(a: int | float, b: int | float) -> int | float:
    """加法函数"""
    return a + b

def divide(a: int | float, b: int | float) -> float:
    """除法函数"""
    if b == 0:
        raise ZeroDivisionError("除数不能为0")
    return a / b
步骤2:编写测试用例 test_calculator.py
from calculator import add, divide

def test_add_normal():
    """测试加法-正常场景"""
    # Arrange 准备测试数据
    num1, num2 = 3, 5
    # Act 执行被测逻辑
    res = add(num1, num2)
    # Assert 断言结果
    assert res == 8

def test_divide_zero():
    """测试除法-除数为0异常场景"""
    # Arrange
    num1, num2 = 10, 0
    # Act + Assert 断言异常抛出
    try:
        divide(num1, num2)
        assert False, "未抛出除零异常"
    except ZeroDivisionError as e:
        assert "除数不能为0" in str(e)
步骤3:执行用例
# 当前目录所有用例
pytest

# 指定文件执行
pytest test_calculator.py -v

# -v 详细日志,-s 打印控制台输出,-q 简洁输出

二、Pytest 核心基础语法(资深工程师必精通)

2.1 原生断言机制(核心优势)

区别于 unittest 的 self.assertXXX,Pytest 支持原生 Python assert 断言,报错信息精准详细,无需冗余语法。

2.1.1 常用断言场景案例

def test_assert_demo():
    # 等值断言
    assert 10 == 10
    # 不等断言
    assert 5 != 3
    # 布尔断言
    assert True
    assert not False
    # 包含断言
    assert "test" in "pytest_test"
    assert 2 in [1,2,3]
    # 空值断言
    assert None is None
    assert "" != None
    # 浮点精准断言(重点:避免精度丢失)
    assert 0.1 + 0.2 == pytest.approx(0.3)

2.1.2 异常断言标准写法

企业级推荐上下文管理器写法,简洁优雅,适配所有异常场景:
import pytest
from calculator import divide

def test_divide_error():
    with pytest.raises(ZeroDivisionError, match="除数不能为0"):
        # 执行会抛出异常的代码
        divide(10, 0)
参数说明:match 支持正则匹配,精准校验异常信息,避免误判。

2.2 测试用例分层:函数/类/模块

Pytest 支持灵活的用例组织方式,适配小型脚本、大型项目分层管理。
# 1. 函数级用例(简单场景)
def test_func_demo():
    assert 1 + 1 == 2

# 2. 类级用例(模块化场景,无__init__方法)
class TestCalculator:
    def test_add(self):
        assert add(2,3) ==5
    
    def test_divide(self):
        assert divide(6,2) ==3.0

# 3. 跳过用例、预期失败用例
@pytest.mark.skip(reason="该功能暂未开发完成")
def test_skip_demo():
    assert 1==2

@pytest.mark.xfail(reason="已知bug,待修复")
def test_xfail_demo():
    assert 1==3

三、Pytest 核心高阶特性(自动化测试核心)

3.1 Fixture 夹具(Pytest 灵魂功能)

Fixture 用于测试前置准备、后置清理、全局数据共享、环境初始化,完全替代 unittest 的 setUp/tearDown,支持灵活的作用域、依赖注入、分层复用。

3.1.1 基础用法(前置+后置)

通过 yield 实现前置执行、后置销毁,是企业唯一推荐写法
import pytest

# 定义夹具
@pytest.fixture
def db_fixture():
    # 【前置操作】连接数据库、初始化数据
    print("\n===== 连接数据库 =====")
    db_conn = "数据库连接对象"
    
    # 返回数据给用例
    yield db_conn
    
    # 【后置操作】关闭连接、清理数据
    print("\n===== 关闭数据库连接 =====")

# 用例中使用夹具(参数注入)
def test_db_query(db_fixture):
    print(f"使用资源:{db_fixture}")
    assert db_fixture == "数据库连接对象"

3.1.2 Fixture 作用域(重点)

控制夹具的复用粒度,优化执行效率,大型项目必备:
  • function(默认):每个用例执行前后都会执行
  • class:每个测试类执行一次
  • module:每个py文件执行一次
  • session:整个测试会话(全局)只执行一次
# 全局夹具,整个测试过程只初始化一次
@pytest.fixture(scope="session")
def global_env():
    print("全局环境初始化")
    yield
    print("全局环境销毁")

3.1.3 夹具依赖与参数传递

支持夹具嵌套依赖、传参,实现复杂环境搭建
@pytest.fixture
def user_data():
    return {"username": "admin", "pwd": "123456"}

# 依赖user_data夹具
@pytest.fixture
def login_env(user_data):
    print(f"使用用户数据:{user_data}")
    return "登录成功"

def test_login(login_env):
    assert login_env == "登录成功"

3.2 参数化测试(批量用例核心)

通过 @pytest.mark.parametrize 实现一条代码、多组用例,覆盖正常、边界、异常场景,大幅精简代码。

3.2.1 基础单参数/多参数案例

import pytest
from calculator import add

# 多组参数批量测试
@pytest.mark.parametrize("a,b,expect", [
    (2,3,5),    # 正常场景
    (-1,1,0),   # 正负边界
    (0,0,0),    # 零值边界
    (1.5,2.5,4.0) # 浮点场景
])
def test_add_batch(a,b,expect):
    assert add(a,b) == expect

3.2.2 参数化别名与用例标记

支持给每组用例命名、单独标记跳过/失败,适配复杂业务场景
@pytest.mark.parametrize(
    "a,b,expect",
    [
        pytest.param(2,3,5,id="正常整数相加"),
        pytest.param(-5,5,0,id="正负抵消边界"),
        pytest.param(0,9,9,id="零值加数"),
    ]
)
def test_add_alias(a,b,expect):
    assert add(a,b) == expect

3.3 用例跳过与预期失败

适配迭代测试、版本兼容、已知bug场景,避免无效报错阻塞测试流程
import sys

# 固定跳过
@pytest.mark.skip(reason="功能迭代中,暂不执行")
def test_skip_fixed():
    assert 1==2

# 条件跳过:仅windows系统跳过
@pytest.mark.skipif(sys.platform == "win32", reason="Linux专属用例")
def test_skip_cond():
    assert True

# 已知bug,预期失败,不统计为错误
@pytest.mark.xfail(reason="接口bug待修复")
def test_xfail_bug():
    assert 100 == 200

四、企业级实战场景案例(接口/单元/UI通用)

4.1 实战1:业务函数单元测试(全覆盖)

针对工具类、业务逻辑函数,实现正常、边界、异常、极值全覆盖测试
被测函数 user_tool.py
def check_user_age(age: int) -> bool:
    """校验用户年龄是否合法(18-60岁)"""
    if not isinstance(age, int):
        raise TypeError("年龄必须为整数")
    if age < 0:
        raise ValueError("年龄不能为负数")
    return 18 <= age <= 60
测试用例 test_user_tool.py
import pytest
from user_tool import check_user_age

# 全覆盖参数化测试
@pytest.mark.parametrize("age,expect", [
    (18, True),  # 下边界
    (60, True),  # 上边界
    (30, True),  # 正常区间
    (17, False), # 小于下限
    (61, False), # 大于上限
])
def test_age_normal(age, expect):
    assert check_user_age(age) == expect

# 异常场景测试
def test_age_type_error():
    with pytest.raises(TypeError, match="年龄必须为整数"):
        check_user_age("18")

def test_age_neg_error():
    with pytest.raises(ValueError, match="年龄不能为负数"):
        check_user_age(-5)

4.2 实战2:HTTP接口自动化测试(主流场景)

基于 Pytest + requests 实现登录接口全场景测试,适配企业接口自动化框架
安装依赖:pip install requests
import pytest
import requests

# 全局夹具:接口基础地址
@pytest.fixture(scope="session")
def base_url():
    return "https://httpbin.org"

# 登录接口参数化测试
@pytest.mark.parametrize("username,pwd,code,msg", [
    ("admin", "123456", 200, "success"), # 正常登录
    ("admin", "wrong", 400, "密码错误"),  # 密码错误
    ("", "123456", 400, "用户名不能为空"),  # 空用户名
])
def test_login_api(base_url, username, pwd, code, msg):
    # Arrange
    url = f"{base_url}/post"
    data = {"username": username, "password": pwd}
    # Act
    res = requests.post(url, json=data)
    # Assert 多层断言(状态码、返回字段、业务逻辑)
    assert res.status_code == 200
    assert "json" in res.json()
    assert res.json()["json"]["username"] == username

4.3 实战3:数据mock测试(隔离依赖)

通过 pytest-mock 模拟数据库、接口、第三方依赖,实现用例无依赖、可独立运行、稳定性100%
被测代码 order_service.py
# 模拟查询订单数据库
def get_order_from_db(order_id: int) -> dict:
    # 真实场景:连接数据库查询
    raise Exception("数据库连接失败")

# 订单业务逻辑
def get_order_info(order_id: int) -> str:
    order = get_order_from_db(order_id)
    if not order:
        return "订单不存在"
    return "订单查询成功"
测试用例(mock隔离数据库依赖)
def test_get_order_info(mocker):
    # mock 数据库函数,替换返回值
    mock_db = mocker.patch("order_service.get_order_from_db")
    mock_db.return_value = {"order_id": 1001, "status": "已支付"}

    # 执行业务逻辑
    from order_service import get_order_info
    res = get_order_info(1001)

    # 断言
    assert res == "订单查询成功"
    mock_db.assert_called_once_with(1001) # 校验函数调用参数和次数

五、测试报告与工程化执行

5.1 生成可视化HTML报告

# 生成html测试报告
pytest test_calculator.py -v --html=report.html --self-contained-html
参数说明:--self-contained-html 打包所有样式,报告可独立打开、分享

5.2 代码覆盖率统计

# 统计覆盖率并生成报告
pytest --cov=. --cov-report=html --cov-report=term
执行后生成 htmlcov 文件夹,打开 index.html 可查看每行代码覆盖率,适配研发提测准入标准。

5.3 分布式并行执行(提速必备)

大型项目用例数千条,单线程执行耗时久,通过 xdist 多进程并行执行
# -n auto 自动根据CPU核心数分配进程
pytest -n auto -v

5.4 用例执行过滤与筛选

# 1. 执行指定标记的用例
pytest -m "smoke" -v

# 2. 模糊匹配用例名称
pytest -k "add" -v

# 3. 只执行失败用例
pytest --lf -v

# 4. 重新运行失败用例3次
pytest --reruns 3 -v

六、Pytest 配置文件(企业工程化核心)

项目根目录创建 pytest.ini 全局配置文件,统一团队执行规则、标记、日志、路径,无需每次敲命令参数。
通用企业级配置模板:
[pytest]
# 测试用例目录
testpaths = tests
# 用例文件命名规则
python_files = test_*.py
# 用例函数命名规则
python_functions = test_*
# 测试类命名规则
python_classes = Test*
# 注册自定义标记
markers =
    smoke: 冒烟测试用例
    regression: 回归测试用例
    api: 接口测试用例
# 失败重跑次数
reruns = 1
# 控制台日志编码
log_encoding = utf-8

七、高频踩坑与解决方案(资深工程师总结)

7.1 用例不执行

原因:命名不规范、文件/函数未以test开头、测试类包含__init__方法 解决方案:严格遵循命名规则,测试类禁止自定义构造方法

7.2 Fixture 数据污染

原因:function作用域夹具未清理数据,用例之间相互影响 解决方案:yield后置强制清理数据,全局夹具做好数据重置

7.3 参数化用例报错不精准

原因:未设置id,无法定位失败用例 解决方案:所有参数化用例添加id别名,精准定位问题场景

7.4 并行执行报错

原因:多进程共享资源(数据库、文件)冲突 解决方案:并行用例隔离资源,session级夹具禁止并行使用

八、企业最佳实践规范

  1. 用例分层:区分冒烟用例、回归用例、全量用例,适配不同测试阶段
  2. 夹具分层:全局夹具(session)、模块夹具(module)、单次夹具(function)分层复用
  3. 用例独立:所有用例相互独立,无执行顺序依赖,可单独运行
  4. 全覆盖断言:不止断言返回值,校验状态码、字段类型、数据长度、异常信息
  5. 数据隔离:通过mock、后置清理实现测试数据隔离,不污染测试环境
  6. 规范命名:用例名称见名知意,格式:test_模块_功能_场景

九、拓展:常用高阶插件

  • pytest-allure:生成高颜值测试报告,适配CI/CD流水线
  • pytest-django/pytest-flask:web框架专属测试插件
  • pytest-selenium:UI自动化测试适配
  • pytest-asyncio:异步接口/函数测试
posted @ 2026-07-30 08:58  xiaolehua  阅读(22)  评论(0)    收藏  举报