Python Pytest 使用指南: #

描述 #

全面的Python pytest测试框架教程,涵盖安装配置、测试用例编写、断言、夹具、参数化测试、测试覆盖率和高级功能,帮助开发者快速掌握专业的Python测试技能。

关键词 #

Python pytest, pytest教程, Python测试框架, pytest入门, pytest高级用法, Python单元测试, pytest夹具, 参数化测试, 测试覆盖率


1. 什么是 pytest? #

pytest 是一个功能强大且灵活的 Python 测试框架,用于编写和运行各种类型的测试,包括单元测试、集成测试和功能测试。它以其简洁的语法、丰富的插件生态系统和强大的断言能力而闻名。

主要特点 #

  • 简单易用的断言语法(使用标准 Python 表达式)
  • 自动发现测试用例
  • 丰富的夹具(fixture)系统
  • 参数化测试支持
  • 强大的插件生态系统
  • 详细的测试报告
  • 支持测试覆盖率分析

2. 安装 pytest #

使用 pip 安装 #

bash
pip install pytest

验证安装 #

bash
pytest --version

安装常用插件 #

bash
# 测试覆盖率
pip install pytest-cov

# 测试报告美化
pip install pytest-html

# 并行测试
pip install pytest-xdist

# 测试排序
pip install pytest-random-order

3. 基本配置 #

pytest.ini 配置文件 #

在项目根目录创建 pytest.ini 文件进行全局配置:

ini
[pytest]
# 测试文件匹配模式
python_files = test_*.py *_test.py

# 测试函数匹配模式
python_functions = test_*

# 测试类匹配模式
python_classes = Test*

# 添加命令行选项
addopts = -v --cov=your_package --cov-report=html

# 搜索测试的目录
testpaths = tests

4. 编写第一个测试用例 #

简单测试函数 #

创建 test_sample.py 文件:

python
def test_addition():
    """测试加法功能"""
    assert 1 + 2 == 3


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

运行测试 #

bash
# 运行单个测试文件
pytest test_sample.py -v

# 运行特定测试函数
pytest test_sample.py::test_addition -v

# 运行所有测试
pytest -v

5. 断言技巧 #

基本断言 #

python
def test_basic_assertions():
    # 相等断言
    assert 1 == 1
    
    # 不等断言
    assert 1 != 2
    
    # 布尔值断言
    assert True
    assert not False
    
    # 包含关系
    assert "a" in "apple"
    assert 3 in [1, 2, 3]
    
    # 类型断言
    assert isinstance("hello", str)
    assert isinstance(123, int)

异常断言 #

python
def test_exception_raising():
    with pytest.raises(ZeroDivisionError):
        1 / 0
    
    # 验证异常信息
    with pytest.raises(ValueError, match="invalid literal"):
        int("not_a_number")

警告断言 #

python
def test_warning_raising():
    with pytest.warns(DeprecationWarning):
        # 代码会产生警告
        pass

6. 测试类 #

基本测试类 #

python
class TestCalculator:
    """测试计算器功能的类"""
    
    def test_addition(self):
        """测试加法"""
        assert 2 + 3 == 5
    
    def test_subtraction(self):
        """测试减法"""
        assert 5 - 2 == 3
    
    def test_multiplication(self):
        """测试乘法"""
        assert 2 * 3 == 6

类级别的固件 #

python
class TestDatabase:
    """测试数据库操作"""
    
    @classmethod
    def setup_class(cls):
        """在测试类开始时执行一次"""
        cls.db = Database.connect()
    
    @classmethod
    def teardown_class(cls):
        """在测试类结束时执行一次"""
        cls.db.disconnect()
    
    def setup_method(self):
        """在每个测试方法开始时执行"""
        self.db.clear_table("users")
    
    def test_create_user(self):
        """测试创建用户"""
        user = self.db.create_user("testuser")
        assert user.id is not None
    
    def test_get_user(self):
        """测试获取用户"""
        self.db.create_user("testuser")
        user = self.db.get_user("testuser")
        assert user.username == "testuser"

7. 夹具(Fixtures) #

基本夹具 #

python
import pytest

@pytest.fixture
def sample_data():
    """提供测试数据的夹具"""
    return [1, 2, 3, 4, 5]


def test_sum(sample_data):
    """使用夹具测试求和功能"""
    assert sum(sample_data) == 15


def test_average(sample_data):
    """使用夹具测试平均值计算"""
    assert sum(sample_data) / len(sample_data) == 3.0

夹具作用域 #

python
@pytest.fixture(scope="function")  # 默认,每个测试函数
@pytest.fixture(scope="class")     # 每个测试类
@pytest.fixture(scope="module")    # 每个模块
@pytest.fixture(scope="session")   # 每个测试会话

夹具依赖 #

python
@pytest.fixture
def db_connection():
    """建立数据库连接"""
    conn = Database.connect()
    yield conn
    conn.close()


@pytest.fixture
def test_user(db_connection):
    """创建测试用户"""
    user = db_connection.create_user("testuser")
    yield user
    db_connection.delete_user(user.id)


@pytest.fixture
def test_post(test_user, db_connection):
    """创建测试帖子"""
    post = db_connection.create_post(test_user.id, "Test Post")
    yield post
    db_connection.delete_post(post.id)

8. 参数化测试 #

基本参数化 #

python
import pytest

@pytest.mark.parametrize("a, b, expected", [
    (1, 2, 3),
    (2, 3, 5),
    (5, 5, 10),
])
def test_addition(a, b, expected):
    """参数化测试加法功能"""
    assert a + b == expected

多参数组合 #

python
@pytest.mark.parametrize("operator", ["+", "-", "*", "/"])
@pytest.mark.parametrize("num1, num2", [(1, 2), (3, 4)])
def test_calculator(operator, num1, num2):
    """测试计算器的不同操作"""
    if operator == "+" and num1 == 1 and num2 == 2:
        assert 1 + 2 == 3
    # 其他测试逻辑...

9. 测试覆盖率 #

安装覆盖率工具 #

bash
pip install pytest-cov

运行带覆盖率的测试 #

bash
# 基本覆盖率报告
pytest --cov=my_package tests/

# 生成HTML覆盖率报告
pytest --cov=my_package --cov-report=html tests/

# 生成XML覆盖率报告(用于CI/CD)
pytest --cov=my_package --cov-report=xml tests/

覆盖率配置 #

pytest.ini 中配置覆盖率:

ini
[pytest]
addopts = --cov=my_package --cov-report=html --cov-report=term-missing

10. 测试标记 #

基本标记 #

python
import pytest

@pytest.mark.slow
def test_long_running_operation():
    """长时间运行的测试"""
    pass


@pytest.mark.skip(reason="功能尚未实现")
def test_unimplemented_feature():
    """跳过未实现的功能测试"""
    pass


@pytest.mark.skipif(sys.version_info < (3, 8), reason="需要Python 3.8+")
def test_python38_feature():
    """只在Python 3.8+上运行的测试"""
    pass

自定义标记 #

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

ini
[pytest]
markers =
    unit: 单元测试
    integration: 集成测试
    performance: 性能测试

使用自定义标记:

python
@pytest.mark.unit
def test_my_function():
    """单元测试"""
    pass


@pytest.mark.integration
@pytest.mark.slow
def test_database_integration():
    """数据库集成测试"""
    pass

运行特定标记的测试:

bash
# 运行所有单元测试
pytest -m unit

# 运行所有单元测试且非慢测试
pytest -m "unit and not slow"

# 运行单元测试或集成测试
pytest -m "unit or integration"

11. 高级功能 #

测试嵌套 #

python
import pytest

class TestAPI:
    """API测试类"""
    
    class TestUsers:
        """用户相关测试"""
        
        def test_create_user(self):
            """测试创建用户"""
            pass
        
        def test_get_user(self):
            """测试获取用户"""
            pass
    
    class TestPosts:
        """帖子相关测试"""
        
        def test_create_post(self):
            """测试创建帖子"""
            pass
        
        def test_get_post(self):
            """测试获取帖子"""
            pass

动态测试生成 #

python
def test_multiplication_table():
    """测试乘法表"""
    for i in range(1, 10):
        for j in range(1, 10):
            result = i * j
            assert result == int(f"{i}*{j}={result}".split("=")[1])

测试依赖 #

python
import pytest

@pytest.mark.dependency(name="test_a")
def test_a():
    """第一个测试"""
    assert True


@pytest.mark.dependency(depends=["test_a"])
def test_b():
    """依赖于test_a的测试"""
    assert True


@pytest.mark.dependency(depends=["test_b"])
def test_c():
    """依赖于test_b的测试"""
    assert True

12. 最佳实践 #

测试用例编写原则 #

  1. 可读性优先:测试用例应该清晰易懂
  2. 独立性:每个测试用例应该独立运行
  3. 单一职责:每个测试用例只测试一个功能点
  4. 可维护性:使用有意义的测试名称和夹具
  5. 边界条件:测试边界值和异常情况

项目结构 #

text
my_project/
├── my_package/
│   ├── __init__.py
│   └── my_module.py
├── tests/
│   ├── __init__.py
│   ├── test_my_module.py
│   └── fixtures/
│       └── __init__.py
├── pytest.ini
└── requirements.txt

CI/CD集成 #

在GitHub Actions中集成pytest:

yaml
# .github/workflows/tests.yml
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.8", "3.9", "3.10", "3.11"]

    steps:
    - uses: actions/checkout@v3
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v4
      with:
        python-version: ${{ matrix.python-version }}
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
        pip install pytest pytest-cov
    - name: Test with pytest
      run: |
        pytest --cov=my_package --cov-report=xml tests/
    - name: Upload coverage to Codecov
      uses: codecov/codecov-action@v3
      with:
        file: ./coverage.xml

13. 常见问题与解决方案 #

测试失败但不知道原因 #

bash
# 详细输出失败信息
pytest -vvs

# 只运行失败的测试
pytest --lf

# 只运行上次运行失败的测试(包括跳过的)
pytest --ff

测试运行缓慢 #

  • 使用 pytest-xdist 并行运行测试
  • 减少测试之间的依赖
  • 使用适当的夹具作用域
  • 避免在测试中进行不必要的IO操作

测试环境隔离 #

  • 使用 pytest-docker 插件管理测试环境
  • 使用 pytest-env 插件设置环境变量
  • 使用 pytest-mock 模拟外部依赖

14. 推荐插件 #

插件名称 用途 安装命令
pytest-cov 测试覆盖率分析 pip install pytest-cov
pytest-html 生成HTML测试报告 pip install pytest-html
pytest-xdist 并行运行测试 pip install pytest-xdist
pytest-mock 模拟对象支持 pip install pytest-mock
pytest-random-order 随机排序测试 pip install pytest-random-order
pytest-docker Docker集成 pip install pytest-docker
pytest-env 环境变量管理 pip install pytest-env
pytest-timeout 测试超时控制 pip install pytest-timeout

15. 总结 #

pytest 是一个功能强大的 Python 测试框架,通过本文的学习,您应该已经掌握了:

  • pytest 的基本概念和特点
  • 安装和配置 pytest
  • 编写各种类型的测试用例
  • 使用断言验证测试结果
  • 利用夹具管理测试资源
  • 使用参数化测试提高效率
  • 分析测试覆盖率
  • 应用最佳实践编写高质量测试

随着您的实践,您将发现 pytest 如何帮助您编写更可靠、更可维护的代码。


16. 资源链接 #