pytest测试框架完全指南

目录

1. 安装与基础配置

安装方法

版本检查

配置文件

2. 编写测试函数

基本结构

断言机制

3. 测试执行与报告

基本执行方式

常用命令行选项

测试报告

4. 测试组织与管理

测试类

模块化测试

5. 高级测试功能

Fixtures 详解

参数化测试

异常测试进阶

6. 测试控制与标记

跳过测试

标记测试

7. 插件生态系统

常用插件

8. 最佳实践

9. 完整示例项目

10. 学习资源


pytest 是 Python 生态中最流行、功能最强大的测试框架之一,它提供了简洁优雅的语法和丰富灵活的测试功能。下面我们将详细介绍 pytest 的核心概念和实用技巧。

1. 安装与基础配置

安装方法

使用 pip 安装最新版本 pytest:

pip install pytest

对于开发环境,建议同时安装常用插件:

pip install pytest pytest-cov pytest-xdist pytest-mock

版本检查

安装完成后可以检查版本:

pytest --version

配置文件

pytest 支持通过 pytest.ini 文件进行配置,例如:

[pytest]
addopts = -v --tb=short
python_files = test_*.py
python_classes = Test*
python_functions = test_*

2. 编写测试函数

基本结构

测试函数应遵循以下命名约定:

  • 测试文件:test_*.py*_test.py
  • 测试函数:test_*()
  • 测试类:Test*

示例测试文件 test_calculator.py

def add(a, b):
    """加法函数"""
    return a + b

def test_add_integers():
    """测试整数加法"""
    assert add(2, 3) == 5
    assert add(-1, 1) == 0

def test_add_floats():
    """测试浮点数加法"""
    assert add(0.1, 0.2) == pytest.approx(0.3)

断言机制

pytest 支持所有标准 Python 断言,并提供增强功能:

# 数值比较
assert result == expected
assert abs(result - expected) < 0.001

# 容器测试
assert item in collection
assert collection1 == collection2

# 异常测试
with pytest.raises(ValueError):
    function_that_raises()

# 使用 pytest.approx 处理浮点数比较
assert 0.1 + 0.2 == pytest.approx(0.3)

3. 测试执行与报告

基本执行方式

# 运行所有测试
pytest

# 运行特定测试文件
pytest test_module.py

# 运行特定测试函数
pytest test_module.py::test_function

# 运行特定测试类
pytest test_module.py::TestClass

常用命令行选项

选项说明
-v详细输出
-x遇到第一个失败就停止
--maxfail=n允许最多 n 次失败
-k "expression"按名称筛选测试
--durations=n显示最慢的 n 个测试
--lf只运行上次失败的测试
--ff先运行失败的测试
--cov生成覆盖率报告

测试报告

生成 HTML 测试报告:

pytest --html=report.html

生成 JUnit 格式报告:

pytest --junitxml=report.xml

4. 测试组织与管理

测试类

将相关测试组织在类中:

class TestMathOperations:
    """测试数学运算"""
    
    def test_addition(self):
        assert 1 + 1 == 2
    
    def test_subtraction(self):
        assert 5 - 3 == 2
    
    def test_multiplication(self):
        assert 2 * 3 == 6

模块化测试

可以使用 Python 包结构组织测试:

tests/
├── __init__.py
├── unit/
│   ├── test_math.py
│   └── test_string.py
└── integration/
    ├── test_database.py
    └── test_api.py

5. 高级测试功能

Fixtures 详解

Fixtures 提供测试所需资源并处理清理工作:

import pytest

@pytest.fixture(scope="module")
def database_connection():
    """创建数据库连接"""
    conn = create_db_connection()
    yield conn  # 测试函数会在此处运行
    conn.close()  # 测试完成后清理

@pytest.fixture
def temporary_file(tmp_path):
    """使用内置 tmp_path fixture 创建临时文件"""
    file = tmp_path / "test.txt"
    file.write_text("test data")
    return file

def test_database_query(database_connection):
    result = database_connection.query("SELECT * FROM users")
    assert len(result) > 0

def test_file_operations(temporary_file):
    assert temporary_file.read_text() == "test data"

参数化测试

使用 @pytest.mark.parametrize 测试多组数据:

import pytest

@pytest.mark.parametrize(
    "input,expected",
    [
        ("3+5", 8),
        ("2*4", 8),
        ("6/2", 3),
        ("10-2", 8),
    ]
)
def test_eval(input, expected):
    assert eval(input) == expected

异常测试进阶

import pytest

def test_divide_by_zero():
    with pytest.raises(ZeroDivisionError) as excinfo:
        1 / 0
    assert str(excinfo.value) == "division by zero"

@pytest.mark.parametrize(
    "value,exception",
    [
        (None, TypeError),
        ("string", ValueError),
        (-1, ValueError)
    ]
)
def test_sqrt(value, exception):
    import math
    with pytest.raises(exception):
        math.sqrt(value)

6. 测试控制与标记

跳过测试

import sys
import pytest

@pytest.mark.skip(reason="功能尚未实现")
def test_unimplemented():
    assert False

@pytest.mark.skipif(sys.version_info < (3, 8), reason="需要 Python 3.8+")
def test_new_syntax():
    assert (x := 5) == 5

标记测试

@pytest.mark.slow
def test_long_running():
    import time
    time.sleep(10)
    assert True

@pytest.mark.integration
def test_database_integration():
    assert database_is_available()

运行标记的测试:

pytest -m "slow or integration"

7. 插件生态系统

pytest 的强大之处在于其丰富的插件生态系统:

常用插件

  1. pytest-cov:代码覆盖率测试

    pytest --cov=myproject --cov-report=html
    

  2. pytest-xdist:并行测试

    pytest -n 4  # 使用4个CPU核心
    

  3. pytest-mock:模拟对象

    def test_api_call(mocker):
        mocker.patch("requests.get", return_value={"status": "ok"})
        assert call_api() == "ok"
    

  4. pytest-django:Django项目测试

  5. pytest-asyncio:异步测试

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

8. 最佳实践

  1. 测试隔离:每个测试应该独立运行,不依赖其他测试的状态
  2. 描述性命名:使用清晰的测试名称说明测试目的
  3. 单一断言原则:每个测试最好只验证一个行为
  4. 避免测试实现细节:测试公共接口而不是内部实现
  5. 使用 fixtures:共享测试资源,减少重复代码
  6. 定期运行测试:集成到持续集成(CI)流程中

9. 完整示例项目

以下是一个典型的项目结构和使用 pytest 的示例:

myproject/
├── src/
│   └── calculator.py
├── tests/
│   ├── __init__.py
│   ├── unit/
│   │   ├── test_math.py
│   │   └── test_operations.py
│   └── integration/
│       └── test_api.py
├── pytest.ini
└── requirements.txt

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("Cannot divide by zero")
        return a / b

tests/unit/test_math.py:

import pytest
from src.calculator import Calculator

@pytest.fixture
def calculator():
    return Calculator()

class TestCalculator:
    def test_add(self, calculator):
        assert calculator.add(2, 3) == 5
    
    def test_subtract(self, calculator):
        assert calculator.subtract(5, 3) == 2
    
    def test_multiply(self, calculator):
        assert calculator.multiply(2, 3) == 6
    
    def test_divide(self, calculator):
        assert calculator.divide(6, 3) == 2
    
    def test_divide_by_zero(self, calculator):
        with pytest.raises(ValueError, match="Cannot divide by zero"):
            calculator.divide(1, 0)

10. 学习资源

  1. 官方文档
  2. pytest 插件列表
  3. Effective Testing with pytest
  4. Python Testing with pytest

通过掌握 pytest 的这些功能,你将能够为 Python 项目编写高效、可维护的测试代码,显著提高代码质量和开发效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值