进阶-pytest配置文件

6 minute read

pytest配置文件全面解析

pytest 配置文件是 pytest 测试框架的核心配置机制,它让您能精细控制测试行为。如下是从基础到高级的全面解析。

一、配置文件类型和优先级

pytest 支持多种配置文件格式,按查找顺序和优先级如下:

  • 1.1 配置文件查找顺序

      1. pytest.ini
      2. pyproject.toml
      3. tox.ini
      4. setup.cfg
    

    查找路径:从当前目录向上查找,直到找到第一个配置文件。

  • 1.2 推荐使用优先级

      # 现代项目(推荐)
      pyproject.toml ✅
    
      # 传统项目
      pytest.ini ✅
    
      # 兼容性项目
      tox.ini ⚠️
    
      # 已弃用
      setup.cfg ❌
    

二、配置文件详解

  • 2.1 pytest.ini(最传统)

      [pytest]
      # 1. 测试发现配置
      testpaths = tests integration_tests
      python_files = test_*.py *_test.py check_*.py
      python_classes = Test* Check*
      python_functions = test_* check_*
    
      # 2. 命令行选项
      addopts = 
          -v                     # 详细输出
          --tb=short             # 简短错误回溯
          --strict-markers       # 严格标记
          --disable-warnings     # 禁用警告
          --maxfail=2            # 失败2个测试后停止
          -x                     # 第一个失败后停止
          --cov                  # 覆盖率报告
          --cov-report=html      # HTML覆盖率报告
          -p no:warnings         # 插件配置
    
      # 3. 标记(markers)注册
      markers =
          slow: 标记为慢速测试
          fast: 快速测试
          smoke: 冒烟测试
          integration: 集成测试
          unit: 单元测试
          serial: 必须串行执行
          windows: 仅Windows平台
          linux: 仅Linux平台
          database: 需要数据库
          api: API测试
          param(id1, id2): 带参数的标记
    
      # 4. 过滤器配置
      filterwarnings =
          error                     # 将警告转为错误
          ignore::UserWarning       # 忽略特定警告
          ignore:.*U.*mode.*:DeprecationWarning
          always::ResourceWarning
    
      # 5. 日志配置
      log_cli = true
      log_cli_level = INFO
      log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
      log_cli_date_format = %Y-%m-%d %H:%M:%S
      log_file = logs/pytest.log
      log_file_level = DEBUG
      log_file_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
      log_file_date_format = %Y-%m-%d %H:%M:%S
    
      # 6. 测试结果输出
      junit_suite_name = Pytest Test Suite
      junit_logging = all
      junit_duration_report = call
      junit_family = xunit2
    
      # 7. 插件配置
      required_plugins = pytest-cov pytest-html pytest-xdist
      asyncio_mode = auto
    
      # 8. 缓存配置
      cache_dir = .pytest_cache
    
      # 9. 自定义配置
      my_custom_setting = value
      timeout = 30.0
    
  • 2.2 pyproject.toml(现代标准)

      # pyproject.toml
      [tool.pytest.ini_options]
      # 1. 测试发现
      testpaths = ["tests", "integration_tests"]
      python_files = ["test_*.py", "*_test.py", "check_*.py"]
      python_classes = ["Test*", "Check*"]
      python_functions = ["test_*", "check_*"]
    
      # 2. 命令行选项
      addopts = [
          "-v",
          "--tb=short",
          "--strict-markers",
          "--disable-warnings",
          "--maxfail=2",
          "-x",
          "--cov",
          "--cov-report=html",
          "-p", "no:warnings"
      ]
    
      # 3. 标记注册
      markers = [
          "slow: marks tests as slow (deselect with '-m \"not slow\"')",
          "fast: marks tests as fast",
          "smoke: smoke tests",
          "integration: integration tests",
          "unit: unit tests",
          "serial: tests that must be run serially",
          "windows: windows only tests",
          "linux: linux only tests",
          "database: tests that require database access",
          "api: API tests"
      ]
    
      # 4. 过滤器
      filterwarnings = [
          "error",
          "ignore::UserWarning",
          "ignore:.*U.*mode.*:DeprecationWarning",
          "always::ResourceWarning"
      ]
    
      # 5. 日志配置
      log_cli = true
      log_cli_level = "INFO"
      log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)"
      log_cli_date_format = "%Y-%m-%d %H:%M:%S"
      log_file = "logs/pytest.log"
      log_file_level = "DEBUG"
    
      # 6. JUnit报告
      junit_suite_name = "Pytest Test Suite"
      junit_logging = "all"
      junit_duration_report = "call"
      junit_family = "xunit2"
    
      # 7. 插件配置
      asyncio_mode = "auto"
      timeout = 30.0
    
      # 8. 自定义配置
      my_custom_setting = "value"
    
      # 9. 覆盖率配置(pytest-cov)
      [tool.coverage.run]
      source = ["src"]
      omit = ["*/tests/*", "*/migrations/*"]
    
      [tool.coverage.report]
      exclude_lines = [
          "pragma: no cover",
          "def __repr__",
          "if self.debug:",
          "if 0:",
          "if __name__ == .__main__.:"
      ]
    
      [tool.coverage.html]
      directory = "htmlcov"
      title = "Test Coverage Report"
    
      # 10. 插件特定配置
      [tool.pytest-xdist]
      addopts = "-n auto"  # 自动检测CPU核心数
    
  • 2.3 tox.ini(兼容tox)

      [tox]
      envlist = py38, py39, py310
      skipsdist = true
    
      [testenv]
      deps =
          pytest
          pytest-cov
      commands = pytest {posargs}
    
      [pytest]
      # 所有pytest配置可以放在这里
      testpaths = tests
      python_files = test_*.py
      addopts = -v
    

三、核心配置项详解

3.1 测试发现配置

[pytest]
# 测试目录(多个用空格分隔)
testpaths = tests integration_tests e2e

# 测试文件模式
python_files = 
    test_*.py      # 前缀模式
    *_test.py      # 后缀模式
    check_*.py     # 自定义模式
    spec_*.py      # BDD风格
    *test.py       # 包含test

# 测试类模式
python_classes = 
    Test*          # 以Test开头
    *Test          # 以Test结尾
    Check*         # 自定义
    Spec*          # BDD风格

# 测试函数/方法模式
python_functions = 
    test_*         # 前缀模式
    *test          # 后缀模式
    check_*        # 自定义
    should_*       # BDD风格
    it_*           # BDD风格

3.2 标记系统(Markers)

[pytest]
markers =
    # 基本分类
    unit: 单元测试
    integration: 集成测试
    e2e: 端到端测试
    smoke: 冒烟测试
    
    # 性能相关
    slow: 运行缓慢的测试 (>1秒)
    fast: 快速测试 (<0.1秒)
    
    # 环境相关
    windows: 仅Windows平台
    linux: 仅Linux平台
    macos: 仅macOS平台
    docker: 需要Docker
    database: 需要数据库
    network: 需要网络
    
    # 功能相关
    api: API测试
    ui: 用户界面测试
    security: 安全测试
    performance: 性能测试
    
    # 执行控制
    serial: 必须串行执行
    parallel: 可以并行执行
    flaky: 不稳定测试
    
    # 自定义参数
    param(name: 参数名, value: 参数值): 带参数的标记

使用示例:

import pytest

@pytest.mark.slow
@pytest.mark.database
@pytest.mark.flaky(reruns=3)
def test_complex_operation():
    pass

@pytest.mark.param(name="user_type", value="admin")
def test_admin_access():
    pass

3.3 警告过滤

[pytest]
filterwarnings =
    # 1. 错误级别
    error                      # 所有警告转为错误
    error::DeprecationWarning  # 特定警告转为错误
    
    # 2. 忽略警告
    ignore                     # 忽略所有警告
    ignore::UserWarning        # 忽略特定类型
    ignore:.*deprecated.*      # 忽略匹配消息
    ignore:DeprecationWarning:.*:django.*  # 忽略特定模块
    
    # 3. 特定动作
    always::UserWarning        # 总是显示
    once::FutureWarning        # 只显示一次
    module::ImportWarning      # 每个模块显示一次
    
    # 4. 复杂模式
    ignore:.*U.*mode.*:DeprecationWarning
    error:.*deprecated.*:DeprecationWarning

3.4 日志配置

[pytest]
# 控制台日志
log_cli = true                          # 启用控制台日志
log_cli_level = INFO                    # 日志级别
log_cli_format = %(asctime)s [%(levelname)8s] %(message)s
log_cli_date_format = %Y-%m-%d %H:%M:%S

# 文件日志
log_file = logs/pytest.log              # 日志文件路径
log_file_level = DEBUG                  # 文件日志级别
log_file_format = %(asctime)s [%(levelname)8s] %(name)s: %(message)s
log_file_date_format = %Y-%m-%d %H:%M:%S

# 自动日志捕获
log_auto_indent = true                  # 自动缩进
log_format = %(levelname)8s %(name)s:%(lineno)d %(message)s
log_date_format = %Y-%m-%d %H:%M:%S

3.5 JUnit XML报告

[pytest]
# 生成JUnit XML报告
junit_suite_name = My Test Suite
junit_logging = all
junit_duration_report = call
junit_family = xunit2

# 高级配置
junit_log_passing_tests = true
junit_duration_report = total
junit_suite_name_remove_path = true

四、高级配置技巧

4.1 环境特定配置

[pytest]
# 基础配置
testpaths = tests
python_files = test_*.py
addopts = -v

# 通过环境变量覆盖
# 开发环境
addopts_dev = --tb=short -x --lf
# CI环境
addopts_ci = --junitxml=report.xml --cov --cov-report=xml
# 生产环境
addopts_prod = --tb=no -q

# 使用环境变量选择
import os
env = os.getenv("PYTEST_ENV", "dev")
if env == "ci":
    addopts = {addopts_ci}
elif env == "prod":
    addopts = {addopts_prod}
else:
    addopts = {addopts_dev}

4.2 动态配置

[pytest]
# 通过conftest.py动态配置
import sys
import os

# 根据Python版本配置
if sys.version_info >= (3, 10):
    addopts = --tb=short -v
else:
    addopts = -v

# 根据操作系统配置
if sys.platform == "win32":
    markers = windows: windows only
elif sys.platform == "linux":
    markers = linux: linux only
elif sys.platform == "darwin":
    markers = macos: macos only

# 根据环境变量配置
if os.getenv("CI"):
    junit_suite_name = CI Test Run
    addopts += --junitxml=junit.xml

4.3 插件集成配置

[pytest]
# pytest-cov 覆盖率
addopts = --cov=src --cov-report=term-missing --cov-report=html

# pytest-xdist 并行测试
addopts = -n auto
xfail_strict = true

# pytest-asyncio
asyncio_mode = auto

# pytest-bdd
bdd_features_base_dir = features/
bdd_strict_gherkin = false

# pytest-django
DJANGO_SETTINGS_MODULE = myproject.settings

五、配置文件组织结构

  • 5.1 多环境配置示例
      project/
      ├── pyproject.toml           # 基础配置
      ├── pytest.ini              # 本地开发
      ├── pytest-ci.ini           # CI环境
      ├── conftest.py             # 共享fixtures
      ├── src/                    # 源代码
      │   └── myapp/
      └── tests/                  # 测试代码
          ├── unit/               # 单元测试
          │   ├── conftest.py     # 单元测试fixtures
          │   └── test_*.py
          ├── integration/        # 集成测试
          │   ├── conftest.py     # 集成测试fixtures
          │   └── test_*.py
          └── e2e/                # 端到端测试
              ├── conftest.py     # E2E测试fixtures
              └── test_*.py
    
  • 5.2 分层配置示例
    目录 pyproject.toml:
      [tool.pytest.ini_options]
      testpaths = ["tests"]
      addopts = "-v"
      markers = [
          "unit: unit tests",
          "integration: integration tests",
          "e2e: end-to-end tests"
      ]
    

    tests/unit/conftest.py:

      # 单元测试特定配置
      import pytest
    
      def pytest_collection_modifyitems(config, items):
          """为单元测试自动添加标记"""
          for item in items:
              if "unit" in str(item.fspath):
                  item.add_marker(pytest.mark.unit)
    

    tests/integration/pytest.ini:

      [pytest]
      # 集成测试特定配置
      addopts = --tb=short -m integration
      timeout = 60
    

六、配置验证和调试

  • 6.1 验证配置文件
      # 查看有效配置
      pytest --help
    
      # 查看已注册的标记
      pytest --markers
    
      # 查看收集的测试
      pytest --collect-only
    
      # 查看配置详情
      pytest --version
      pytest --config
    
  • 6.2 调试配置文件
      # conftest.py
      def pytest_configure(config):
          """调试配置"""
          print("=== pytest_configure ===")
          print(f"Rootdir: {config.rootdir}")
          print(f"Inifile: {config.inifile}")
          print(f"Addopts: {config.option.addopts}")
          print(f"Testpaths: {config.getini('testpaths')}")
          print(f"Markers: {config.getini('markers')}")
    

七、最佳实践建议

  • 7.1 配置选择
      项目类型            推荐配置          说明
      --------------     -------------    ----------------
      现代Python项目     pyproject.toml     PEP 621标准,统一管理
      Django项目         pytest.ini        传统,社区习惯
      混合项目           pytest.ini        兼容性好
      库/包              pyproject.toml    与构建工具集成
    
  • 7.2 配置文件组织
      # 1. 基础配置(必须)
      [pytest]
      testpaths = tests
      addopts = -v --tb=short
      markers = 基本标记
    
      # 2. 环境特定(按需)
      filterwarnings = 警告过滤
      log_cli = 日志配置
      junit_* = 报告配置
    
      # 3. 插件集成(按需)
      asyncio_mode = asyncio配置
      cov_* = 覆盖率配置
    
  • 7.3 版本控制
      # .gitignore
      .pytest_cache/
      .coverage
      htmlcov/
      *.xml
      *.html
      logs/*.log
      !logs/.gitkeep
    

八、实用配置片段

  • 8.1 开发环境配置
      [pytest]
      testpaths = tests
      python_files = test_*.py
      addopts = 
          -v
          --tb=short
          -x
          --lf
          --capture=no
      markers =
          unit: 单元测试
          integration: 集成测试
          slow: 慢速测试
      filterwarnings =
          ignore::DeprecationWarning
          ignore::FutureWarning
    
  • 8.2 CI环境配置
      [pytest]
      testpaths = tests
      addopts = 
          -v
          --tb=line
          --strict-markers
          --junitxml=junit.xml
          --cov=src
          --cov-report=xml
          --cov-report=term-missing
      junit_suite_name = CI Test Suite
    
  • 8.3 完整企业级示例
      # pyproject.toml
      [build-system]
      requires = ["setuptools>=61.0"]
      build-backend = "setuptools.build_meta"
    
      [project]
      name = "myproject"
      version = "1.0.0"
      dependencies = []
    
      [tool.pytest.ini_options]
      testpaths = ["tests"]
      python_files = ["test_*.py", "*_test.py"]
      python_classes = ["Test*"]
      python_functions = ["test_*", "should_*"]
      addopts = [
          "-v",
          "--strict-markers",
          "--tb=short",
          "--durations=10",
          "--disable-warnings",
          "-p", "no:warnings"
      ]
    
      markers = [
          "unit: Unit tests (< 0.1s)",
          "integration: Integration tests (0.1-1s)",
          "e2e: End-to-end tests (> 1s)",
          "slow: Slow tests (> 5s, run separately)",
          "flaky(reruns=3): Flaky tests that can be retried",
          "windows: Windows-specific tests",
          "linux: Linux-specific tests",
          "macos: macOS-specific tests",
          "docker: Requires Docker",
          "database: Requires database",
          "api: API tests",
          "security: Security tests"
      ]
    
      filterwarnings = [
          "error",
          "ignore::DeprecationWarning",
          "ignore::PendingDeprecationWarning",
          "ignore::FutureWarning:.*:",
          "ignore:.*U.*mode.*:DeprecationWarning"
      ]
    
      log_cli = true
      log_cli_level = "INFO"
      log_cli_format = "%(asctime)s [%(levelname)-8s] %(name)-20s: %(message)s"
      log_cli_date_format = "%H:%M:%S"
    
      junit_suite_name = "MyProject Test Suite"
      junit_family = "xunit2"
      junit_logging = "system-out"
    
      [tool.coverage.run]
      source = ["src"]
      omit = [
          "*/tests/*",
          "*/migrations/*",
          "*/__pycache__/*",
          "setup.py"
      ]
    
      [tool.coverage.report]
      exclude_lines = [
          "pragma: no cover",
          "def __repr__",
          "if self.debug:",
          "if 0:",
          "if __name__ == .__main__.:",
          "raise AssertionError",
          "raise NotImplementedError"
      ]
    
      [tool.coverage.html]
      directory = "coverage_html"
      title = "Coverage Report"
    

九、总结

pytest 配置文件是测试框架的控制中心,合理配置可以:

  • 统一测试规范:确保团队使用相同的测试发现和运行规则
  • 提高测试效率:通过合理配置减少等待时间,优化输出
  • 增强测试可维护性:清晰的标记系统和配置结构
  • 集成CI/CD:生成标准化的测试报告
  • 管理测试环境:不同环境使用不同配置

核心建议:

  • 新项目使用 pyproject.toml
  • 明确配置测试发现规则
  • 使用标记系统组织测试
  • 合理配置日志和报告
  • 为不同环境准备不同配置
  • 文档化配置项,特别是自定义标记*