Pytest Fixture 详解
Fixture 是 pytest 最强大的功能之一,用于提供测试所需的依赖资源(如数据库连接、临时文件、模拟对象等),并支持复用、作用域控制和自动清理。以下是全面详解:
1. 基本用法
定义 Fixture
使用 @pytest.fixture
装饰器定义:
python
import pytest
@pytest.fixture
def database_connection():
conn = create_db_connection() # 初始化资源
yield conn # 返回资源,测试结束后执行清理
conn.close() # 清理操作(yield 之后的部分)
使用 Fixture
在测试函数中作为参数传入:
python
def test_query_data(database_connection):
result = database_connection.execute("SELECT * FROM users")
assert len(result) > 0
2. Fixture 作用域(Scope)
通过 scope
参数控制 Fixture 的生命周期:
作用域 | 说明 | 使用场景 |
---|---|---|
function |
每个测试函数运行一次(默认) | 轻量级资源(如临时变量) |
class |
每个测试类运行一次 | 类共享资源(如配置对象) |
module |
每个测试模块(文件)运行一次 | 全局配置(如日志初始化) |
package |
每个测试包运行一次 | 包级共享资源 |
session |
整个测试会话只运行一次 | 昂贵资源(如数据库连接池) |
示例:
python
@pytest.fixture(scope="module")
def shared_config():
return {"timeout": 30}
3. Fixture 的 Setup/Teardown
使用 yield
或 addfinalizer
实现资源清理:
yield 方式(推荐)
python
@pytest.fixture
def temp_file():
file = open("/tmp/test.txt", "w")
yield file # 测试中使用 file 对象
file.close() # 测试结束后关闭文件
addfinalizer 方式(更灵活)
python
@pytest.fixture
def temp_file(request):
file = open("/tmp/test.txt", "w")
def cleanup():
file.close()
request.addfinalizer(cleanup) # 注册清理函数
return file
4. Fixture 参数化
用 @pytest.mark.parametrize
对 Fixture 参数化:
python
@pytest.fixture(params=["user1", "user2", "admin"])
def user(request):
return create_user(request.param) # 根据参数生成不同用户
def test_user_permissions(user):
assert user.has_permission("read")
5. 自动使用 Fixture(autouse)
设置 autouse=True
,无需显式调用即可自动运行:
python
@pytest.fixture(autouse=True)
def setup_logging():
logging.basicConfig(level=logging.INFO) # 所有测试自动启用日志
def test_example():
assert True # 此测试会自动执行 setup_logging
6. Fixture 依赖注入
Fixture 可以依赖其他 Fixture:
python
@pytest.fixture
def db():
return Database()
@pytest.fixture
def api_client(db): # 依赖 db fixture
return APIClient(db)
def test_api(api_client):
response = api_client.get("/users")
assert response.status_code == 200
7. 动态 Fixture
通过 request
对象动态调整 Fixture:
python
@pytest.fixture
def dynamic_data(request):
marker = request.node.get_closest_marker("data")
if marker:
return marker.args[0] # 获取测试标记的参数
return {"default": 42}
@pytest.mark.data({"custom": 100})
def test_dynamic(dynamic_data):
assert dynamic_data["custom"] == 100
8. 内置 Fixture
pytest 提供了一些常用内置 Fixture:
Fixture | 说明 |
---|---|
tmp_path |
临时目录路径(pathlib.Path ) |
tmpdir |
临时目录(Legacy py.path.local ) |
capsys |
捕获 stdout/stderr |
monkeypatch |
动态修改对象或环境变量 |
request |
访问测试上下文(如参数、标记) |
示例:
python
def test_tmp_file(tmp_path):
file = tmp_path / "test.txt"
file.write_text("Hello")
assert file.read_text() == "Hello"
9. Fixture 覆盖与插件
覆盖 Fixture
在 conftest.py
中定义的 Fixture 可被当前目录及子目录的测试共享:
project/
├── conftest.py # 定义全局 Fixture
├── tests/
│ ├── conftest.py # 覆盖父级 Fixture
│ └── test_demo.py
Fixture 插件
通过插件扩展 Fixture(如 pytest-django
提供 django_db
):
python
def test_django_model(django_db):
user = User.objects.create(name="Alice")
assert user.name == "Alice"
10. 最佳实践
- 将 Fixture 定义在
conftest.py
中以便复用。 - 避免 Fixture 逻辑过于复杂,保持单一职责。
- 优先使用
yield
而非addfinalizer
(代码更简洁)。 - 用
scope
减少重复初始化 (如数据库连接池用session
作用域)。
通过灵活使用 Fixture,你可以实现:
- 资源复用(减少重复代码)
- 依赖管理(清晰表达测试需求)
- 环境隔离(避免测试间干扰)
掌握后,测试代码会变得更简洁、可维护! 🚀