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. 最佳实践 #
测试用例编写原则 #
- 可读性优先:测试用例应该清晰易懂
- 独立性:每个测试用例应该独立运行
- 单一职责:每个测试用例只测试一个功能点
- 可维护性:使用有意义的测试名称和夹具
- 边界条件:测试边界值和异常情况
项目结构 #
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 如何帮助您编写更可靠、更可维护的代码。