09.pytest的常用的第三方插件

Pytest 常用的第三方插件详解

1. 前言

1.1 什么是 Pytest 插件

Pytest 插件是扩展 pytest 功能的第三方库。Pytest 本身提供了强大的测试框架基础,而插件则在此基础上添加了更多实用的功能,比如生成HTML报告、并行执行测试、代码覆盖率统计、失败重试等。

1.2 为什么要使用插件

  • 提高效率:并行执行、失败重试等功能可以节省时间
  • 增强功能:添加pytest本身没有的功能,如HTML报告、代码覆盖率
  • 改善体验:美化输出、立即显示失败信息等让测试更友好
  • 专业工具:使用专业插件让测试更规范、更专业

1.3 如何安装插件

基本安装命令

# 使用 pip 安装单个插件
pip install pytest-html

# 使用 pip 安装多个插件
pip install pytest-html pytest-xdist pytest-cov

# 从 requirements.txt 安装
pip install -r requirements.txt

查看已安装的插件

# 查看所有已安装的 pytest 插件
pytest --trace-config

# 或者
pip list | grep pytest

1.4 插件配置文件

pytest.ini 配置示例

[pytest]
# 添加命令行选项
addopts = -v --tb=short
# 指定测试目录
testpaths = tests
# 指定测试文件匹配模式
python_files = test_*.py
# 指定测试类匹配模式
python_classes = Test*
# 指定测试函数匹配模式
python_functions = test_*

pyproject.toml 配置示例

[tool.pytest.ini_options]
addopts = "-v --tb=short"
testpaths = ["tests"]
python_files = "test_*.py"
python_classes = "Test*"
python_functions = "test_*"

2. pytest-html:生成 HTML 测试报告

2.1 插件简介

pytest-html 是 pytest 最常用的插件之一,它可以生成美观的 HTML 格式测试报告,让测试结果更直观、更易读。

2.2 安装方法

pip install pytest-html

2.3 基本用法

2.3.1 生成基本 HTML 报告

命令

pytest --html=report.html

示例

# 运行测试并生成报告
pytest test_example.py --html=report.html

# 生成报告到指定目录
pytest --html=reports/report.html

2.3.2 查看报告

生成报告后,直接用浏览器打开 report.html 文件即可查看。

2.4 详细示例

示例 1:基本使用

测试文件:test_calculator.py

def test_addition():
    """测试加法"""
    assert 1 + 1 == 2

def test_subtraction():
    """测试减法"""
    assert 5 - 3 == 2

def test_multiplication():
    """测试乘法"""
    assert 2 * 3 == 6

def test_division():
    """测试除法"""
    assert 10 / 2 == 5

def test_division_by_zero():
    """测试除零错误"""
    try:
        result = 1 / 0
    except ZeroDivisionError:
        assert True

运行命令

pytest test_calculator.py --html=report.html --self-contained-html

参数说明

  • --html=report.html:生成HTML报告
  • --self-contained-html:生成自包含的HTML(包含所有CSS和JS,方便分享)

示例 2:自定义报告标题

命令

pytest --html=report.html --title="我的测试报告"

示例 3:在报告中包含额外信息

测试文件:test_with_extra_info.py

import pytest

def test_with_extra_info():
    """测试带额外信息"""
    # 使用 pytest 的 extra 功能添加额外信息
    pytest.extra = [
        ("环境", "测试环境"),
        ("版本", "1.0.0"),
        ("执行人", "张三")
    ]
    assert 1 + 1 == 2

2.5 高级配置

2.5.1 在 pytest.ini 中配置

[pytest]
addopts = --html=report.html --self-contained-html

2.5.2 自定义报告模板

# conftest.py
import pytest
from pytest_html import extras

@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
    outcome = yield
    report = outcome.get_result()
    if report.when == "call":
        # 添加截图(如果有)
        if hasattr(item, "screenshot"):
            extra = getattr(report, "extra", [])
            extra.append(extras.image(item.screenshot))
            report.extra = extra

2.6 报告内容说明

HTML 报告通常包含:

  • 测试摘要:总测试数、通过数、失败数、跳过数
  • 测试详情:每个测试的执行时间、状态、错误信息
  • 环境信息:Python版本、pytest版本、操作系统等
  • 统计图表:可视化测试结果

3. pytest-xdist:并行执行测试

3.1 插件简介

pytest-xdist 允许你并行运行测试,大大缩短测试执行时间。特别适合有大量测试用例的项目。

3.2 安装方法

pip install pytest-xdist

3.3 基本用法

3.3.1 使用所有 CPU 核心

命令

pytest -n auto

这会自动检测 CPU 核心数并使用所有核心并行执行测试。

3.3.2 指定并行进程数

命令

# 使用 4 个进程
pytest -n 4

# 使用 2 个进程
pytest -n 2

3.4 详细示例

示例 1:基本并行执行

测试文件:test_parallel.py

import time

def test_1():
    """测试1 - 模拟耗时操作"""
    time.sleep(1)
    assert True

def test_2():
    """测试2 - 模拟耗时操作"""
    time.sleep(1)
    assert True

def test_3():
    """测试3 - 模拟耗时操作"""
    time.sleep(1)
    assert True

def test_4():
    """测试4 - 模拟耗时操作"""
    time.sleep(1)
    assert True

def test_5():
    """测试5 - 模拟耗时操作"""
    time.sleep(1)
    assert True

串行执行(不使用 xdist)

pytest test_parallel.py -v
# 执行时间:约 5 秒(5个测试 × 1秒)

并行执行(使用 xdist)

pytest test_parallel.py -n auto -v
# 执行时间:约 1-2 秒(取决于CPU核心数)

示例 2:查看并行执行详情

命令

pytest -n 4 -v --dist=worksteal

参数说明

  • -n 4:使用4个进程
  • --dist=worksteal:使用工作窃取算法分配任务(默认)

示例 3:按文件并行

命令

pytest -n auto --dist=loadfile

参数说明

  • --dist=loadfile:按文件分配,同一文件的测试在同一进程中执行

3.5 并行模式说明

3.5.1 load(默认模式)

每个进程独立执行测试,适合大多数情况。

pytest -n 4 --dist=load

3.5.2 loadscope

按作用域(模块、类)分配测试。

pytest -n 4 --dist=loadscope

3.5.3 loadfile

按文件分配测试,同一文件的测试在同一进程。

pytest -n 4 --dist=loadfile

3.5.4 worksteal

使用工作窃取算法,动态分配任务。

pytest -n 4 --dist=worksteal

3.6 注意事项

  1. 测试独立性:并行测试要求测试用例之间相互独立,不能有依赖关系
  2. 共享资源:注意文件、数据库等共享资源的并发访问问题
  3. fixture 作用域:注意 fixture 的作用域,避免并发问题

示例:有依赖的测试(不适合并行)

# ❌ 错误示例:测试之间有依赖
class TestCounter:
    count = 0  # 类变量,会被多个进程共享

    def test_increment_1(self):
        TestCounter.count += 1
        assert TestCounter.count == 1

    def test_increment_2(self):
        TestCounter.count += 1
        assert TestCounter.count == 2  # 可能失败,因为并行执行

示例:独立的测试(适合并行)

# ✅ 正确示例:测试之间独立
def test_addition():
    assert 1 + 1 == 2

def test_subtraction():
    assert 5 - 3 == 2

def test_multiplication():
    assert 2 * 3 == 6

4. pytest-cov:代码覆盖率统计

4.1 插件简介

pytest-cov 用于统计测试的代码覆盖率,帮助你了解哪些代码被测试覆盖,哪些代码没有被测试。

4.2 安装方法

pip install pytest-cov

注意:pytest-cov 依赖于 coverage 库,通常会自动安装。

4.3 基本用法

4.3.1 生成覆盖率报告

命令

pytest --cov=src

参数说明

  • --cov=src:统计 src 目录的代码覆盖率

4.3.2 生成 HTML 覆盖率报告

命令

pytest --cov=src --cov-report=html

这会在 htmlcov 目录生成 HTML 报告。

4.3.3 在终端显示覆盖率

命令

pytest --cov=src --cov-report=term

4.4 详细示例

示例 1:基本覆盖率统计

项目结构

project/
├── src/
│   ├── calculator.py
│   └── utils.py
└── tests/
    └── test_calculator.py

源代码:src/calculator.py

class Calculator:
    def add(self, a, b):
        """加法"""
        return a + b

    def subtract(self, a, b):
        """减法"""
        return a - b

    def multiply(self, a, b):
        """乘法"""
        return a * b

    def divide(self, a, b):
        """除法"""
        if b == 0:
            raise ValueError("除数不能为0")
        return a / b

测试文件:tests/test_calculator.py

from src.calculator import Calculator

def test_add():
    calc = Calculator()
    assert calc.add(1, 2) == 3

def test_subtract():
    calc = Calculator()
    assert calc.subtract(5, 3) == 2

# 注意:没有测试 multiply 和 divide 方法

运行覆盖率统计

pytest tests/test_calculator.py --cov=src --cov-report=term-missing

输出示例

Name                    Stmts   Miss  Cover   Missing
-----------------------------------------------------
src/calculator.py          10      4    60%   7-9, 12
src/utils.py                5      5     0%   1-5
-----------------------------------------------------
TOTAL                      15      9    40%

说明

  • Stmts:总语句数
  • Miss:未覆盖的语句数
  • Cover:覆盖率百分比
  • Missing:未覆盖的行号

示例 2:生成 HTML 报告

命令

pytest --cov=src --cov-report=html --cov-report=term

查看报告: 打开 htmlcov/index.html 文件,可以看到:

  • 每个文件的覆盖率
  • 每行代码的覆盖情况(绿色=已覆盖,红色=未覆盖)
  • 覆盖率统计图表

示例 3:设置覆盖率阈值

命令

pytest --cov=src --cov-fail-under=80

说明

  • --cov-fail-under=80:如果覆盖率低于80%,测试失败

在 pytest.ini 中配置

[pytest]
addopts = --cov=src --cov-report=html --cov-report=term --cov-fail-under=80

示例 4:排除特定文件或目录

命令

pytest --cov=src --cov-report=term --ignore=src/tests

在 .coveragerc 文件中配置

[run]
source = src
omit = 
    */tests/*
    */test_*.py
    */__pycache__/*
    */venv/*

4.5 覆盖率报告格式

4.5.1 term(终端显示)

pytest --cov=src --cov-report=term

4.5.2 term-missing(显示未覆盖的行)

pytest --cov=src --cov-report=term-missing

4.5.3 html(HTML报告)

pytest --cov=src --cov-report=html

4.5.4 xml(XML报告,用于CI/CD)

pytest --cov=src --cov-report=xml

4.5.5 json(JSON报告)

pytest --cov=src --cov-report=json

4.6 高级配置

4.6.1 .coveragerc 配置文件

[run]
source = src
omit = 
    */tests/*
    */__pycache__/*
    */venv/*

[report]
exclude_lines =
    pragma: no cover
    def __repr__
    raise AssertionError
    raise NotImplementedError
    if __name__ == .__main__.:
    if TYPE_CHECKING:
    @abstractmethod

4.6.2 在代码中排除特定行

def example_function():
    if some_condition:  # pragma: no cover
        return "rare case"
    return "normal case"

5. pytest-timeout:测试超时控制

5.1 插件简介

pytest-timeout 可以为测试用例设置超时时间,防止测试无限期运行。如果测试在指定时间内没有完成,会被自动终止。

5.2 安装方法

pip install pytest-timeout

5.3 基本用法

5.3.1 全局超时设置

命令

pytest --timeout=10

说明:所有测试用例的超时时间为 10 秒。

5.3.2 使用装饰器设置单个测试超时

测试文件:test_timeout.py

import pytest
import time

@pytest.mark.timeout(5)  # 5秒超时
def test_quick():
    """快速测试"""
    assert 1 + 1 == 2

@pytest.mark.timeout(2)  # 2秒超时
def test_slow():
    """慢速测试(会超时)"""
    time.sleep(3)  # 睡眠3秒,超过2秒超时
    assert True

运行

pytest test_timeout.py -v

输出

test_timeout.py::test_quick PASSED
test_timeout.py::test_slow TIMEOUT (超过2秒)

5.4 详细示例

示例 1:全局超时

测试文件:test_global_timeout.py

import time

def test_fast():
    """快速测试"""
    time.sleep(0.5)
    assert True

def test_medium():
    """中等速度测试"""
    time.sleep(2)
    assert True

def test_slow():
    """慢速测试(会超时)"""
    time.sleep(5)  # 超过3秒全局超时
    assert True

运行命令

pytest test_global_timeout.py --timeout=3 -v

示例 2:使用装饰器

测试文件:test_decorator_timeout.py

import pytest
import time

@pytest.mark.timeout(1)
def test_with_timeout_1():
    """1秒超时"""
    time.sleep(0.5)
    assert True

@pytest.mark.timeout(2)
def test_with_timeout_2():
    """2秒超时"""
    time.sleep(1.5)
    assert True

@pytest.mark.timeout(0.5)
def test_timeout_fail():
    """0.5秒超时,但需要1秒,会失败"""
    time.sleep(1)
    assert True

运行

pytest test_decorator_timeout.py -v

示例 3:在 pytest.ini 中配置

[pytest]
timeout = 10
timeout_method = thread

配置说明

  • timeout = 10:默认超时时间10秒
  • timeout_method = thread:使用线程方式(默认)
  • timeout_method = signal:使用信号方式(仅Unix系统)

5.5 超时方法说明

5.5.1 thread 方法(默认)

使用线程来监控超时,适用于所有平台。

pytest --timeout=10 --timeout-method=thread

5.5.2 signal 方法

使用信号来监控超时,仅适用于Unix系统(Linux、macOS),Windows不支持。

pytest --timeout=10 --timeout-method=signal

注意:signal 方法在某些情况下可能更可靠,但只支持Unix系统。

5.6 禁用超时

方法 1:使用装饰器

import pytest

@pytest.mark.timeout(0)  # 0表示禁用超时
def test_no_timeout():
    import time
    time.sleep(100)  # 不会超时
    assert True

方法 2:使用命令行参数

pytest --timeout=0  # 禁用所有超时

6. pytest-mock:Mock 功能增强

6.1 插件简介

pytest-mock 是 pytest 的 mock 插件,它提供了 mocker fixture,让 mock 操作更简单、更符合 pytest 的风格。

6.2 安装方法

pip install pytest-mock

6.3 基本用法

6.3.1 使用 mocker fixture

测试文件:test_mock.py

def test_with_mock(mocker):
    """使用 mocker fixture"""
    # Mock 一个函数
    mock_func = mocker.patch('module.function')
    mock_func.return_value = 'mocked value'

    # 使用被 mock 的函数
    result = module.function()
    assert result == 'mocked value'
    mock_func.assert_called_once()

6.4 详细示例

示例 1:Mock 函数

源代码:src/api.py

import requests

def get_user_data(user_id):
    """获取用户数据"""
    response = requests.get(f'https://api.example.com/users/{user_id}')
    return response.json()

测试文件:tests/test_api.py

import pytest
from src.api import get_user_data

def test_get_user_data(mocker):
    """测试获取用户数据(Mock网络请求)"""
    # Mock requests.get
    mock_get = mocker.patch('src.api.requests.get')

    # 设置返回值
    mock_response = mocker.Mock()
    mock_response.json.return_value = {'id': 1, 'name': '张三'}
    mock_get.return_value = mock_response

    # 调用函数
    result = get_user_data(1)

    # 断言
    assert result == {'id': 1, 'name': '张三'}
    mock_get.assert_called_once_with('https://api.example.com/users/1')

示例 2:Mock 类方法

源代码:src/database.py

class Database:
    def connect(self):
        """连接数据库"""
        # 实际数据库连接代码
        pass

    def query(self, sql):
        """查询数据库"""
        # 实际查询代码
        pass

测试文件:tests/test_database.py

import pytest
from src.database import Database

def test_database_query(mocker):
    """测试数据库查询(Mock数据库连接)"""
    # Mock Database 类的 connect 方法
    mock_connect = mocker.patch.object(Database, 'connect')
    mock_connect.return_value = None

    # Mock query 方法
    mock_query = mocker.patch.object(Database, 'query')
    mock_query.return_value = [{'id': 1, 'name': 'test'}]

    # 使用
    db = Database()
    db.connect()
    result = db.query('SELECT * FROM users')

    # 断言
    assert result == [{'id': 1, 'name': 'test'}]
    mock_connect.assert_called_once()
    mock_query.assert_called_once_with('SELECT * FROM users')

示例 3:Mock 环境变量

源代码:src/config.py

import os

def get_api_key():
    """获取API密钥"""
    return os.environ.get('API_KEY', 'default_key')

测试文件:tests/test_config.py

import pytest
from src.config import get_api_key

def test_get_api_key(mocker):
    """测试获取API密钥"""
    # Mock 环境变量
    mocker.patch.dict('os.environ', {'API_KEY': 'test_key_123'})

    # 调用函数
    result = get_api_key()

    # 断言
    assert result == 'test_key_123'

示例 4:Mock 时间

源代码:src/utils.py

from datetime import datetime

def get_current_time():
    """获取当前时间"""
    return datetime.now()

测试文件:tests/test_utils.py

import pytest
from datetime import datetime
from src.utils import get_current_time

def test_get_current_time(mocker):
    """测试获取当前时间(Mock时间)"""
    # Mock datetime.now
    fixed_time = datetime(2024, 1, 1, 12, 0, 0)
    mocker.patch('src.utils.datetime')
    from src.utils import datetime as mock_datetime
    mock_datetime.now.return_value = fixed_time

    # 调用函数
    result = get_current_time()

    # 断言
    assert result == fixed_time

示例 5:Mock 副作用(Side Effects)

def test_mock_side_effect(mocker):
    """测试 Mock 副作用"""
    # 创建一个函数,每次调用返回不同的值
    mock_func = mocker.patch('module.function')
    mock_func.side_effect = [1, 2, 3]

    # 第一次调用返回 1
    assert module.function() == 1
    # 第二次调用返回 2
    assert module.function() == 2
    # 第三次调用返回 3
    assert module.function() == 3

示例 6:Mock 异常

def test_mock_exception(mocker):
    """测试 Mock 抛出异常"""
    mock_func = mocker.patch('module.function')
    mock_func.side_effect = ValueError('测试异常')

    # 调用应该抛出异常
    with pytest.raises(ValueError, match='测试异常'):
        module.function()

6.5 Mock 方法说明

6.5.1 mocker.patch()

Mock 一个对象或函数。

mock_obj = mocker.patch('module.object')

6.5.2 mocker.patch.object()

Mock 对象的属性或方法。

mocker.patch.object(SomeClass, 'method')

6.5.3 mocker.patch.dict()

Mock 字典(常用于环境变量)。

mocker.patch.dict('os.environ', {'KEY': 'value'})

6.5.4 mocker.Mock()

创建一个 Mock 对象。

mock_obj = mocker.Mock()
mock_obj.method.return_value = 'value'

6.5.5 mocker.MagicMock()

创建一个 MagicMock 对象(支持更多魔术方法)。

mock_obj = mocker.MagicMock()

6.6 断言方法

# 断言被调用
mock_func.assert_called()

# 断言被调用一次
mock_func.assert_called_once()

# 断言被调用指定次数
mock_func.assert_called_once_with(arg1, arg2)

# 断言调用参数
mock_func.assert_called_with(arg1, arg2)

# 断言调用次数
assert mock_func.call_count == 2

7. pytest-rerunfailures:失败重试

7.1 插件简介

pytest-rerunfailures 允许你自动重试失败的测试用例。这对于处理不稳定的测试(如网络请求、并发问题等)非常有用。

7.2 安装方法

pip install pytest-rerunfailures

7.3 基本用法

7.3.1 全局重试设置

命令

pytest --reruns 3

说明:所有失败的测试会重试 3 次。

7.3.2 使用装饰器设置单个测试重试

测试文件:test_rerun.py

import pytest
import random

@pytest.mark.flaky(reruns=3)  # 重试3次
def test_unstable():
    """不稳定的测试"""
    # 模拟不稳定的测试(50%概率失败)
    if random.random() < 0.5:
        assert False, "随机失败"
    assert True

7.4 详细示例

示例 1:全局重试

测试文件:test_network.py

import requests
import random

def test_api_request():
    """测试API请求(可能因为网络问题失败)"""
    # 模拟网络请求
    response = requests.get('https://api.example.com/data')
    assert response.status_code == 200

运行命令

pytest test_network.py --reruns 3 --reruns-delay 2

参数说明

  • --reruns 3:重试3次
  • --reruns-delay 2:每次重试前等待2秒

示例 2:使用装饰器

测试文件:test_flaky.py

import pytest
import random

@pytest.mark.flaky(reruns=5, reruns_delay=1)
def test_flaky_test():
    """不稳定的测试"""
    # 模拟不稳定的测试
    result = random.random()
    assert result > 0.3, f"随机值 {result} 太小"

运行

pytest test_flaky.py -v

输出示例

test_flaky.py::test_flaky_test RERUN
test_flaky.py::test_flaky_test RERUN
test_flaky.py::test_flaky_test PASSED

示例 3:只重试特定异常

测试文件:test_specific_exception.py

import pytest

@pytest.mark.flaky(reruns=3, reruns_delay=1, only_rerun=['AssertionError'])
def test_with_specific_exception():
    """只重试 AssertionError"""
    # 如果抛出其他异常,不会重试
    assert 1 == 2  # 会重试

示例 4:在 pytest.ini 中配置

[pytest]
addopts = --reruns 3 --reruns-delay 2

7.5 高级用法

7.5.1 条件重试

import pytest

@pytest.mark.flaky(
    reruns=3,
    reruns_delay=1,
    condition=lambda result: result.outcome == "failed"
)
def test_conditional_rerun():
    """条件重试"""
    assert 1 == 2

7.5.2 重试延迟

@pytest.mark.flaky(reruns=3, reruns_delay=2.5)
def test_with_delay():
    """重试前等待2.5秒"""
    assert False

7.6 注意事项

  1. 不要滥用重试:重试应该用于处理真正不稳定的测试,不应该用来掩盖测试中的bug
  2. 重试次数:不要设置过多的重试次数,通常2-3次就足够了
  3. 重试延迟:对于网络请求等,可以设置适当的延迟时间

8. pytest-ordering:控制测试执行顺序

8.1 插件简介

pytest-ordering 允许你控制测试用例的执行顺序。默认情况下,pytest 不保证测试的执行顺序,但有时我们需要按特定顺序执行测试。

8.2 安装方法

pip install pytest-ordering

8.3 基本用法

8.3.1 使用装饰器标记顺序

测试文件:test_ordering.py

import pytest

@pytest.mark.run(order=1)
def test_first():
    """第一个执行"""
    print("执行第一个测试")
    assert True

@pytest.mark.run(order=2)
def test_second():
    """第二个执行"""
    print("执行第二个测试")
    assert True

@pytest.mark.run(order=3)
def test_third():
    """第三个执行"""
    print("执行第三个测试")
    assert True

运行

pytest test_ordering.py -v -s

输出

test_ordering.py::test_first 执行第一个测试 PASSED
test_ordering.py::test_second 执行第二个测试 PASSED
test_ordering.py::test_third 执行第三个测试 PASSED

8.4 详细示例

示例 1:基本顺序控制

测试文件:test_login_flow.py

import pytest

@pytest.mark.run(order=1)
def test_login():
    """1. 先登录"""
    print("执行登录")
    assert True

@pytest.mark.run(order=2)
def test_access_dashboard():
    """2. 访问仪表板"""
    print("访问仪表板")
    assert True

@pytest.mark.run(order=3)
def test_logout():
    """3. 最后登出"""
    print("执行登出")
    assert True

示例 2:使用相对顺序

import pytest

@pytest.mark.run(order=-1)  # 最后执行
def test_cleanup():
    """清理工作"""
    print("清理")
    assert True

@pytest.mark.run(order=1)  # 第一个执行
def test_setup():
    """准备工作"""
    print("准备")
    assert True

@pytest.mark.run(order=2)  # 中间执行
def test_main():
    """主要测试"""
    print("主要测试")
    assert True

示例 3:多个测试使用相同顺序

import pytest

@pytest.mark.run(order=1)
def test_setup_1():
    """准备1"""
    assert True

@pytest.mark.run(order=1)
def test_setup_2():
    """准备2(与setup_1同顺序,执行顺序不确定)"""
    assert True

@pytest.mark.run(order=2)
def test_main():
    """主要测试"""
    assert True

8.5 顺序标记说明

8.5.1 正数顺序

@pytest.mark.run(order=1)  # 第一个
@pytest.mark.run(order=2)  # 第二个
@pytest.mark.run(order=10) # 第十个

8.5.2 负数顺序

@pytest.mark.run(order=-1)  # 最后一个
@pytest.mark.run(order=-2)  # 倒数第二个

8.5.3 混合使用

@pytest.mark.run(order=1)   # 第一个
@pytest.mark.run(order=2)   # 第二个
@pytest.mark.run(order=-1)  # 最后一个
@pytest.mark.run(order=-2)  # 倒数第二个

8.6 注意事项

  1. 测试独立性:虽然可以控制顺序,但测试应该尽量保持独立
  2. 不推荐过度使用:如果测试之间有强依赖,考虑使用 fixture 而不是顺序控制
  3. 性能影响:顺序控制可能会影响并行执行

9. pytest-sugar:美化输出

9.1 插件简介

pytest-sugar 可以美化 pytest 的输出,让测试结果更直观、更易读。它会显示进度条、颜色、表情符号等。

9.2 安装方法

pip install pytest-sugar

9.3 基本用法

安装后无需额外配置,直接运行 pytest 即可看到美化后的输出。

普通输出

test_example.py ...                                          [100%]

美化输出(pytest-sugar)

test_example.py ✓✓✓                                         [100%]

9.4 详细示例

示例 1:基本使用

测试文件:test_sugar.py

def test_pass():
    """通过的测试"""
    assert True

def test_fail():
    """失败的测试"""
    assert False

def test_skip():
    """跳过的测试"""
    import pytest
    pytest.skip("跳过这个测试")

运行

pytest test_sugar.py -v

美化输出特点

  • ✓ 表示通过的测试(绿色)
  • ✗ 表示失败的测试(红色)
  • s 表示跳过的测试(黄色)
  • 进度条显示
  • 颜色高亮

9.5 功能特点

  1. 进度条:实时显示测试进度
  2. 颜色:使用颜色区分不同状态的测试
  3. 符号:使用直观的符号(✓、✗等)
  4. 即时反馈:测试完成后立即显示结果

10. pytest-instafail:立即显示失败信息

10.1 插件简介

pytest-instafail 会在测试失败时立即显示失败信息,而不是等到所有测试完成后才显示。这对于长时间运行的测试套件非常有用。

10.2 安装方法

pip install pytest-instafail

10.3 基本用法

命令

pytest --instafail

10.4 详细示例

示例 1:基本使用

测试文件:test_instafail.py

import time

def test_pass_1():
    """通过的测试1"""
    time.sleep(1)
    assert True

def test_fail():
    """失败的测试"""
    time.sleep(1)
    assert False, "这个测试失败了"

def test_pass_2():
    """通过的测试2"""
    time.sleep(1)
    assert True

不使用 instafail

pytest test_instafail.py -v
# 需要等待所有测试完成后才显示失败信息

使用 instafail

pytest test_instafail.py -v --instafail
# 测试失败时立即显示失败信息,不需要等待

10.5 功能特点

  1. 即时反馈:测试失败时立即显示
  2. 节省时间:不需要等待所有测试完成
  3. 便于调试:可以立即看到失败原因

11. pytest-asyncio:异步测试支持

11.1 插件简介

pytest-asyncio 为 pytest 提供异步测试支持,允许你测试异步函数和协程。

11.2 安装方法

pip install pytest-asyncio

11.3 基本用法

11.3.1 使用 pytest.mark.asyncio 装饰器

测试文件:test_async.py

import pytest
import asyncio

@pytest.mark.asyncio
async def test_async_function():
    """测试异步函数"""
    await asyncio.sleep(0.1)
    assert True

@pytest.mark.asyncio
async def test_async_calculation():
    """测试异步计算"""
    result = await asyncio.sleep(0.1)
    assert result is None

11.4 详细示例

示例 1:测试异步函数

源代码:src/async_utils.py

import asyncio

async def fetch_data(url):
    """异步获取数据"""
    await asyncio.sleep(0.1)  # 模拟网络请求
    return {"url": url, "data": "test data"}

async def process_data(data):
    """异步处理数据"""
    await asyncio.sleep(0.1)
    return data.upper() if isinstance(data, str) else data

测试文件:tests/test_async_utils.py

import pytest
from src.async_utils import fetch_data, process_data

@pytest.mark.asyncio
async def test_fetch_data():
    """测试异步获取数据"""
    result = await fetch_data("https://example.com")
    assert result["url"] == "https://example.com"
    assert result["data"] == "test data"

@pytest.mark.asyncio
async def test_process_data():
    """测试异步处理数据"""
    result = await process_data("hello")
    assert result == "HELLO"

示例 2:测试异步类方法

源代码:src/async_client.py

import asyncio

class AsyncClient:
    async def connect(self):
        """异步连接"""
        await asyncio.sleep(0.1)
        return True

    async def send(self, message):
        """异步发送消息"""
        await asyncio.sleep(0.1)
        return f"Sent: {message}"

测试文件:tests/test_async_client.py

import pytest
from src.async_client import AsyncClient

@pytest.mark.asyncio
async def test_async_client():
    """测试异步客户端"""
    client = AsyncClient()
    connected = await client.connect()
    assert connected is True

    result = await client.send("Hello")
    assert result == "Sent: Hello"

示例 3:使用 fixture

测试文件:test_async_fixture.py

import pytest
import asyncio

@pytest.fixture
async def async_data():
    """异步 fixture"""
    await asyncio.sleep(0.1)
    return {"key": "value"}

@pytest.mark.asyncio
async def test_with_async_fixture(async_data):
    """使用异步 fixture"""
    assert async_data["key"] == "value"

示例 4:在 pytest.ini 中配置

[pytest]
asyncio_mode = auto

配置说明

  • asyncio_mode = auto:自动检测异步测试
  • asyncio_mode = strict:严格模式,必须显式标记

11.5 异步模式说明

11.5.1 auto 模式(默认)

自动检测异步测试函数。

[pytest]
asyncio_mode = auto

11.5.2 strict 模式

必须显式使用 @pytest.mark.asyncio 标记。

[pytest]
asyncio_mode = strict

12. pytest-json-report:生成 JSON 报告

12.1 插件简介

pytest-json-report 可以生成 JSON 格式的测试报告,方便程序化处理和集成到其他工具中。

12.2 安装方法

pip install pytest-json-report

12.3 基本用法

命令

pytest --json-report --json-report-file=report.json

12.4 详细示例

示例 1:生成 JSON 报告

测试文件:test_json_report.py

def test_pass():
    """通过的测试"""
    assert True

def test_fail():
    """失败的测试"""
    assert False

def test_skip():
    """跳过的测试"""
    import pytest
    pytest.skip("跳过")

运行

pytest test_json_report.py --json-report --json-report-file=report.json

生成的 JSON 报告示例

{
  "created": 1234567890.123,
  "duration": 0.123,
  "exitcode": 1,
  "root": "/path/to/project",
  "environment": {
    "Python": "3.9.0",
    "Platform": "Windows-10"
  },
  "summary": {
    "passed": 1,
    "failed": 1,
    "skipped": 1,
    "total": 3
  },
  "tests": [
    {
      "nodeid": "test_json_report.py::test_pass",
      "outcome": "passed",
      "duration": 0.001
    },
    {
      "nodeid": "test_json_report.py::test_fail",
      "outcome": "failed",
      "duration": 0.002,
      "call": {
        "longrepr": "AssertionError: assert False"
      }
    }
  ]
}

示例 2:在 CI/CD 中使用

# .github/workflows/test.yml
- name: Run tests
  run: |
    pytest --json-report --json-report-file=report.json

- name: Upload report
  uses: actions/upload-artifact@v2
  with:
    name: test-report
    path: report.json

13. 插件组合使用

13.1 常用组合

在实际项目中,通常会组合使用多个插件。以下是一些常见的组合:

组合 1:基础测试套件

pytest 
  --html=report.html 
  --self-contained-html 
  --cov=src 
  --cov-report=html 
  --cov-report=term 
  -v

功能

  • 生成HTML报告
  • 统计代码覆盖率
  • 详细输出

组合 2:快速开发

pytest 
  --instafail 
  --tb=short 
  -v

功能

  • 立即显示失败
  • 简短的错误信息
  • 详细输出

组合 3:CI/CD 环境

pytest 
  -n auto 
  --html=report.html 
  --self-contained-html 
  --cov=src 
  --cov-report=xml 
  --cov-report=term 
  --json-report 
  --json-report-file=report.json 
  --junitxml=junit.xml

功能

  • 并行执行
  • 多种报告格式
  • 适合CI/CD集成

13.2 pytest.ini 完整配置示例

[pytest]
# 基础配置
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*

# 输出配置
addopts = 
    -v
    --tb=short
    --strict-markers
    --html=report.html
    --self-contained-html
    --cov=src
    --cov-report=html
    --cov-report=term-missing
    --cov-fail-under=80
    --instafail

# 超时配置
timeout = 300
timeout_method = thread

# 重试配置
reruns = 2
reruns_delay = 1

# 异步配置
asyncio_mode = auto

# Markers
markers =
    slow: 标记慢速测试
    integration: 标记集成测试
    unit: 标记单元测试

13.3 requirements.txt 示例

pytest>=7.0.0
pytest-html>=3.0.0
pytest-xdist>=2.5.0
pytest-cov>=4.0.0
pytest-timeout>=2.1.0
pytest-mock>=3.10.0
pytest-rerunfailures>=11.0.0
pytest-ordering>=0.6
pytest-sugar>=0.9.6
pytest-instafail>=0.4.2
pytest-asyncio>=0.21.0
pytest-json-report>=1.5.0

14. 插件选择建议

14.1 必装插件

  1. pytest-html:生成HTML报告,几乎每个项目都需要
  2. pytest-cov:代码覆盖率统计,保证代码质量

14.2 推荐插件

  1. pytest-xdist:如果测试用例较多,强烈推荐
  2. pytest-mock:如果需要Mock,推荐使用
  3. pytest-timeout:防止测试无限期运行
  4. pytest-sugar:美化输出,提升开发体验

14.3 按需安装

  1. pytest-rerunfailures:如果有不稳定的测试
  2. pytest-ordering:如果需要控制测试顺序(不推荐过度使用)
  3. pytest-asyncio:如果项目使用异步编程
  4. pytest-instafail:如果测试套件运行时间较长

发表评论