pytest 是 Python 中一个功能强大且广泛使用的测试框架,用于编写和运行单元测试、集成测试以及其他类型的测试。它以简单易用、灵活性和强大的插件生态系统而闻名,适合从小型项目到大型应用的测试需求。相比 Python 内置的 unittest 模块,pytest 提供更简洁的语法、更少的样板代码和丰富的功能。本文将详细介绍 pytest 库,包括其核心功能、用法、常见特性、插件系统以及最佳实践。


1. pytest 简介

pytest 是一个开源的测试框架,支持 Python 2.7 和 3.5 及以上版本。它的设计目标是让测试代码更简洁、易读,同时提供强大的功能,如自动测试发现、丰富的断言、参数化测试和插件扩展。

主要特点
  • 简洁的语法:无需继承测试类,普通函数即可作为测试用例。
  • 自动测试发现:自动查找和运行符合命名规则的测试文件和函数。
  • 强大的断言:使用 Python 的 assert 语句,无需特殊断言方法。
  • 参数化测试:支持测试用例的参数化,减少重复代码。
  • 插件生态:丰富的插件支持,如覆盖率分析、并行测试等。
  • 详细的报告:提供清晰的测试结果和失败信息。
安装

使用 pip 安装 pytest

pip install pytest

检查安装:

pytest --version

2. 基本用法

pytest 的使用非常直观,以下是编写和运行测试的基本步骤。

1) 编写测试

pytest 的测试用例是普通的 Python 函数,遵循以下命名约定:

  • 测试文件以 test_ 开头或以 _test 结尾(如 test_example.py)。
  • 测试函数以 test_ 开头(如 test_function)。
  • 测试类以 Test 开头,方法以 test_ 开头。

示例test_math.py):

def add(a, b):
    return a + b

def test_add():
    assert add(2, 3) == 5
    assert add(-1, 1) == 0
    assert add(0, 0) == 0
2) 运行测试

在终端中运行:

pytest

pytest 会自动发现当前目录及其子目录中符合命名规则的测试文件并执行。输出类似:

============================= test session starts ==============================
platform linux -- Python 3.10.12, pytest-8.3.2, pluggy-1.5.0
collected 1 item

test_math.py .                                                           [100%]

============================== 1 passed in 0.01s ===============================
3) 常用命令行选项
  • pytest -v:详细输出,显示每个测试的名称。
  • pytest test_math.py:运行特定文件。
  • pytest test_math.py::test_add:运行特定测试函数。
  • pytest -k "add":运行包含“add”关键字的测试。
  • pytest --collect-only:仅收集测试用例,不执行。
  • pytest -x:在第一个失败时停止。
  • pytest --maxfail=2:在指定数量的失败后停止。

3. 核心功能

pytest 提供了丰富的功能,以下是核心特性和用法。

1) 断言

pytest 使用 Python 的内置 assert 语句,无需特殊的断言方法。失败时,pytest 会提供详细的错误信息。

示例

def test_string():
    assert "hello".upper() == "HELLO"
    assert "hello" + " world" == "hello world"

失败输出:

>       assert "hello".upper() == "HELLO!"
E       AssertionError: assert 'HELLO' == 'HELLO!'
E         - HELLO
E         + HELLO!
2) 自动测试发现

pytest 自动查找测试文件和函数:

  • 文件test_*.py*_test.py
  • 函数:以 test_ 开头的函数。
  • :以 Test 开头的类,其方法以 test_ 开头。

示例test_example.py):

class TestExample:
    def test_one(self):
        assert 1 + 1 == 2
    
    def test_two(self):
        assert 2 * 2 == 4

运行 pytest 会发现并执行 test_onetest_two

3) 异常测试

使用 pytest.raises 测试代码是否抛出预期异常。

示例

import pytest

def divide(a, b):
    return a / b

def test_divide_by_zero():
    with pytest.raises(ZeroDivisionError):
        divide(10, 0)
4) 参数化测试

使用 @pytest.mark.parametrize 为测试函数提供多组输入数据,减少重复代码。

示例

import pytest

@pytest.mark.parametrize("a, b, expected", [
    (2, 3, 5),
    (-1, 1, 0),
    (0, 0, 0),
])
def test_add(a, b, expected):
    assert add(a, b) == expected

运行后,test_add 会为每组参数运行一次,生成三个测试用例。

5) 夹具(Fixtures)

夹具是 pytest 的核心功能,用于提供测试所需的资源(如数据、对象、临时文件)。夹具通过 @pytest.fixture 装饰器定义。

示例

import pytest

@pytest.fixture
def sample_data():
    return [1, 2, 3]

def test_sum(sample_data):
    assert sum(sample_data) == 6
  • 夹具作用域:控制夹具的生命周期(如 functionmodulesession)。

    @pytest.fixture(scope="module")
    def db_connection():
        conn = create_connection()  # 假设的函数
        yield conn  # 返回资源
        conn.close()  # 清理
    
  • 自动使用夹具:通过 autouse=True 自动应用夹具。

    @pytest.fixture(autouse=True)
    def setup():
        print("\nSetup")
        yield
        print("Teardown")
    
6) 标记(Markers)

使用 @pytest.mark 为测试添加标记,用于分类或选择性运行。

示例

import pytest

@pytest.mark.slow
def test_slow_operation():
    assert True

@pytest.mark.unit
def test_unit():
    assert 1 + 1 == 2

运行特定标记的测试:

pytest -m slow  # 仅运行标记为 slow 的测试
pytest -m "not slow"  # 排除 slow 标记的测试

自定义标记需在 pytest.ini 中注册:

[pytest]
markers =
    slow: marks tests as slow
    unit: marks unit tests
7) 跳过和预期失败
  • @pytest.mark.skip:跳过测试。
  • @pytest.mark.skipif:根据条件跳过。
  • @pytest.mark.xfail:标记测试预期失败。

示例

import sys

@pytest.mark.skip(reason="Not implemented yet")
def test_not_ready():
    assert False

@pytest.mark.skipif(sys.version_info < (3, 8), reason="Requires Python 3.8+")
def test_new_feature():
    assert True

@pytest.mark.xfail
def test_may_fail():
    assert False  # 失败但不计入失败统计

4. 插件系统

pytest 的插件生态系统非常强大,提供了额外的功能。常用插件包括:

1) pytest-cov
  • 用途:测量测试覆盖率。
  • 安装pip install pytest-cov
  • 用法
    pytest --cov=src --cov-report=html
    
    生成 HTML 覆盖率报告。
2) pytest-mock
  • 用途:简化 mock 对象的使用。
  • 安装pip install pytest-mock
  • 示例
    def test_mock(mocker):
        mocked = mocker.patch("module.some_function", return_value=42)
        assert module.some_function() == 42
    
3) pytest-asyncio
  • 用途:支持异步测试。
  • 安装pip install pytest-asyncio
  • 示例
    import pytest
    import asyncio
    
    @pytest.mark.asyncio
    async def test_async_function():
        await asyncio.sleep(0.1)
        assert True
    
4) pytest-xdist
  • 用途:并行运行测试,加速执行。
  • 安装pip install pytest-xdist
  • 用法
    pytest -n 4  # 使用 4 个进程并行运行
    
5) 自定义插件

开发者可以编写自定义插件,扩展 pytest 功能。插件通常定义夹具、钩子函数或标记。

示例pytest_custom.py):

def pytest_addoption(parser):
    parser.addoption("--custom", action="store", default="value")

@pytest.fixture
def custom_option(request):
    return request.config.getoption("--custom")

使用:

pytest --custom=hello

5. 配置 pytest

pytest 支持通过配置文件(如 pytest.inipyproject.toml)定制行为。

示例pytest.ini):

[pytest]
python_files = test_*.py
python_functions = test_*
addopts = -v --cov=src --cov-report=html
markers =
    slow: slow tests

示例pyproject.toml):

[tool.pytest.ini_options]
python_files = "test_*.py"
addopts = "-v"

6. 最佳实践

  1. 编写简洁的测试

    • 每个测试函数专注于单一职责。
    • 使用描述性名称,如 test_add_handles_negative_numbers
  2. 使用夹具管理资源

    • 将初始化和清理逻辑放入夹具,保持测试代码干净。
    • 示例:
      @pytest.fixture
      def temp_file(tmp_path):
          f = tmp_path / "test.txt"
          f.write_text("hello")
          return f
      
  3. 参数化测试减少重复

    • 使用 @pytest.mark.parametrize 覆盖多种输入场景。
    • 示例:
      @pytest.mark.parametrize("input, expected", [("a", "A"), ("b", "B")])
      def test_upper(input, expected):
          assert input.upper() == expected
      
  4. 避免过度依赖 mock

    • 仅在必要时(如测试外部依赖)使用 mock,避免测试过于复杂。
  5. 定期检查覆盖率

    • 使用 pytest-cov 确保测试覆盖关键代码路径。
  6. 集成到 CI/CD

    • pytest 集成到 GitHub Actions 或 Jenkins,确保每次提交运行测试。
    • 示例(GitHub Actions):
      name: Run Tests
      on: [push]
      jobs:
        test:
          runs-on: ubuntu-latest
          steps:
            - uses: actions/checkout@v3
            - uses: actions/setup-python@v4
              with:
                python-version: "3.10"
            - run: pip install pytest pytest-cov
            - run: pytest --cov=src
      
  7. 处理 flaky 测试

    • 使用 pytest-rerunfailures 重试不稳定的测试:
      pip install pytest-rerunfailures
      pytest --reruns 3
      

7. 注意事项

  1. 测试隔离

    • 确保测试之间独立,避免状态泄漏。
    • 使用夹具或 tmp_path 管理临时资源。
  2. 性能优化

    • 对于大型测试套件,使用 pytest-xdist 并行执行。
    • 避免在夹具中执行昂贵操作,设置适当的 scope
  3. 版本兼容性

    • 确保 pytest 和插件版本与 Python 版本兼容。
    • 检查插件文档,某些功能可能需要特定版本。
  4. 调试失败

    • 使用 pytest --pdb 进入调试器:
      pytest --pdb
      
    • 查看详细输出:pytest -vv.
  5. 避免过度标记

    • 仅为必要分类使用标记,保持配置简单。

8. 总结

pytest 是 Python 生态中最强大的测试框架之一,凭借简洁的语法、自动测试发现、参数化测试、夹具和插件系统,极大地提高了测试开发的效率。核心功能包括:

  • 简洁断言:使用 assert 提供详细错误信息。
  • 夹具:管理测试资源,支持作用域和自动使用。
  • 参数化:通过 @pytest.mark.parametrize 简化多场景测试。
  • 插件:如 pytest-covpytest-asyncio 扩展功能。
  • 配置:通过 pytest.inipyproject.toml 定制行为。
Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐