下面是一个完整的 pytest 测试项目结构示例,包含了最佳实践和常见配置:
项目结构
Plain Text
project-name/
├── src/ # 应用程序源代码
│ └── app.py
├── tests/ # 测试目录
│ ├── unit/ # 单元测试
│ │ ├── test_app.py
│ │ └── test_utils.py
│ ├── integration/ # 集成测试
│ │ └── test_api.py
│ ├── conftest.py # 共享的fixture和配置
│ └── pytest.ini # pytest配置文件
├── .gitignore
├── requirements.txt # 项目依赖
└── setup.py # 安装脚本(可选)
关键文件内容
1. src/app.py (示例应用代码)
python
def add(a, b):
return a + b
def subtract(a, b):
return a - b
def multiply(a, b):
return a * b
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
2. tests/unit/test_app.py (单元测试)
python
import pytest
from src.app import add, subtract, multiply, divide
class TestMathOperations:
def test_add(self):
assert add(2, 3) == 5
assert add(-1, 1) == 0
def test_subtract(self):
assert subtract(5, 3) == 2
assert subtract(3, 5) == -2
def test_multiply(self):
assert multiply(3, 4) == 12
assert multiply(0, 5) == 0
def test_divide(self):
assert divide(10, 2) == 5
def test_divide_by_zero(self):
with pytest.raises(ValueError) as exc_info:
divide(10, 0)
assert "Cannot divide by zero" in str(exc_info.value)
3. tests/conftest.py (共享fixture)
python
import pytest
# 共享fixture示例
@pytest.fixture
def sample_data():
return {"a": 10, "b": 5}
# 配置pytest钩子
def pytest_addoption(parser):
parser.addoption("--slow", action="store_true", default=False,
help="Run slow tests")
def pytest_collection_modifyitems(config, items):
if not config.getoption("--slow"):
skip_slow = pytest.mark.skip(reason="need --slow option to run")
for item in items:
if "slow" in item.keywords:
item.add_marker(skip_slow)
4. tests/pytest.ini (配置文件)
ini
[pytest]
addopts = -v --cov=src --cov-report=html
testpaths = tests/unit tests/integration
markers =
slow: marks tests as slow (deselect with '-m "not slow"')
5. requirements.txt (依赖文件)
Plain Text
pytest==7.1.2
pytest-cov==3.0.0
requests==2.27.1 # 用于API测试
运行测试
基本命令
bash
# 运行所有测试
pytest
# 运行特定目录下的测试
pytest tests/unit
# 运行特定文件中的测试
pytest tests/unit/test_app.py
# 运行特定类中的测试
pytest tests/unit/test_app.py::TestMathOperations
# 运行特定测试方法
pytest tests/unit/test_app.py::TestMathOperations::test_add
高级选项
bash
# 生成覆盖率报告
pytest --cov=src
# 只运行标记为slow的测试
pytest -m slow
# 跳过标记为slow的测试
pytest -m "not slow"
# 在失败时启动调试器
pytest --pdb
# 并行运行测试(需要pytest-xdist)
pytest -n auto
最佳实践
-
命名约定:
-
测试文件:
test_*.py或*_test.py -
测试类:
Test* -
测试方法:
test_*
-
-
测试组织:
-
按功能模块分组测试
-
将单元测试与集成测试分离
-
使用标记(markers)分类测试
-
-
使用fixture:
-
避免重复设置代码
-
创建可重用的测试环境
-
使用
conftest.py共享fixture
-
-
参数化测试:
python
@pytest.mark.parametrize("a,b,expected", [ (1, 2, 3), (-1, 1, 0), (10, 20, 30) ]) def test_add(a, b, expected): assert add(a, b) == expected -
测试覆盖率:
-
使用
pytest-cov生成覆盖率报告 -
目标覆盖关键业务逻辑
-
但不要追求100%覆盖率(可能产生低价值测试)
-
-
持续集成:
-
在CI/CD流程中运行测试
-
设置测试失败时的警报
-
使用测试结果作为质量门禁
-
常见插件
-
pytest-cov:代码覆盖率报告
-
pytest-xdist:并行运行测试
-
pytest-mock:简化mock使用
-
pytest-django:Django项目支持
-
pytest-asyncio:异步测试支持
-
pytest-html:生成HTML报告
调试技巧
-
使用
pytest --trace在测试开始时启动调试器 -
添加
print()语句输出调试信息 -
使用
pytest -s禁用输出捕获(显示print输出) -
在失败测试中使用
pdb:python
def test_failing(): result = some_function() import pdb; pdb.set_trace() # 在此设置断点 assert result == expected
这个结构提供了一个健壮的测试基础,可以根据项目需求进行调整和扩展。