三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

3.Pytest 夹具(Fixture)

3.Pytest 夹具(Fixture)

Pytest 夹具(Fixture)

一、什么是夹具(Fixture)

夹具是 pytest 中用于提供测试所需资源(数据、对象、环境等)的函数。它可以帮助你:

  • 避免重复代码
  • 管理测试依赖
  • 控制测试执行顺序
  • 自动清理资源

二、基础使用

1. 定义和调用夹具

import pytest# 定义夹具
@pytest.fixture
def sample_data():return {"name": "Alice", "age": 30}# 使用夹具
def test_user_info(sample_data):assert sample_data["name"] == "Alice"assert sample_data["age"] == 30

2. 夹具作用域

# 函数级(默认)- 每个测试函数运行一次
@pytest.fixture(scope="function")
def db_connection():conn = create_connection()yield connconn.close()# 类级 - 每个测试类运行一次
@pytest.fixture(scope="class")
def class_data():return {"class_id": 101}# 模块级 - 每个模块运行一次
@pytest.fixture(scope="module")
def module_data():return {"module": "test_module"}# 会话级 - 整个测试会话运行一次
@pytest.fixture(scope="session")
def global_config():return {"env": "test"}

三、高级特性

1. 夹具依赖

@pytest.fixture
def user():return {"id": 1, "name": "Bob"}@pytest.fixture
def user_with_profile(user):profile = {"email": "bob@example.com"}user.update(profile)return userdef test_user_profile(user_with_profile):assert user_with_profile["email"] == "bob@example.com"assert user_with_profile["name"] == "Bob"

2. 参数化夹具

@pytest.fixture(params=[1, 2, 3])
def number(request):return request.paramdef test_numbers(number):assert number in [1, 2, 3]print(f"Testing with: {number}")# 这个测试会运行3次

3. 使用 yield 实现 teardown

@pytest.fixture
def database():# Setupdb = Database()db.connect()print("Database connected")yield db  # 提供夹具值# Teardowndb.close()print("Database closed")def test_database(database):database.insert("data")assert database.count() == 1

4. 自动使用的夹具

@pytest.fixture(autouse=True)
def setup_teardown():print("\nBefore test")yieldprint("After test")# 每个测试都会自动执行这个夹具

四、实战示例

示例1:Web应用测试

import pytest
from fastapi.testclient import TestClient@pytest.fixture(scope="session")
def app():from main import appreturn app@pytest.fixture(scope="module")
def client(app):with TestClient(app) as test_client:yield test_client@pytest.fixture
def auth_client(client):# 创建认证后的客户端response = client.post("/login", json={"username": "testuser","password": "testpass"})token = response.json()["token"]client.headers.update({"Authorization": f"Bearer {token}"})return clientdef test_get_user(auth_client):response = auth_client.get("/user/me")assert response.status_code == 200assert response.json()["username"] == "testuser"

示例2:数据库测试

import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker@pytest.fixture(scope="session")
def engine():engine = create_engine("sqlite:///:memory:")Base.metadata.create_all(engine)yield engineBase.metadata.drop_all(engine)@pytest.fixture
def db_session(engine):connection = engine.connect()transaction = connection.begin()Session = sessionmaker(bind=connection)session = Session()yield sessionsession.close()transaction.rollback()connection.close()def test_create_user(db_session):user = User(name="Alice")db_session.add(user)db_session.commit()assert db_session.query(User).count() == 1

示例3:文件操作测试

import pytest
import tempfile
import os@pytest.fixture
def temp_file():# 创建临时文件fd, path = tempfile.mkstemp()yield path# 清理os.close(fd)os.unlink(path)def test_file_operations(temp_file):with open(temp_file, 'w') as f:f.write("test data")with open(temp_file, 'r') as f:content = f.read()assert content == "test data"

五、最佳实践

1. 使用 conftest.py 共享夹具

# conftest.py
import pytest@pytest.fixture
def shared_fixture():return "shared across tests"

2. 夹具命名规范

# 好的命名
@pytest.fixture
def database_connection():  # 清晰的名称pass# 不好的命名
@pytest.fixture
def db():  # 太简短,不够清晰pass

3. 使用 fixture 工厂

@pytest.fixture
def make_user():def _make_user(name, age):return {"name": name, "age": age}return _make_userdef test_users(make_user):user1 = make_user("Alice", 30)user2 = make_user("Bob", 25)assert user1["name"] == "Alice"assert user2["name"] == "Bob"

4. 使用 pytest.mark.usefixtures

@pytest.mark.usefixtures("setup_db", "cleanup_files")
class TestUserAPI:def test_create_user(self):# 自动使用 setup_db 和 cleanup_files 夹具passdef test_delete_user(self):pass

六、调试技巧

# 1. 查看夹具执行顺序
pytest --setup-show# 2. 查看所有可用夹具
pytest --fixtures# 3. 查看特定夹具
pytest --fixtures -v | grep fixture_name# 4. 在夹具中添加调试信息
@pytest.fixture
def debug_fixture():print("Setting up")yieldprint("Tearing down")

七、常见陷阱

1. 修改不可变对象

# 错误:修改共享的不可变对象
@pytest.fixture(scope="module")
def shared_list():return []  # 这个列表会在所有测试间共享def test_append(shared_list):shared_list.append(1)  # 会影响其他测试# 正确:使用函数作用域
@pytest.fixture
def fresh_list():return []

2. 异步夹具

import pytest
import asyncio@pytest.fixture
async def async_fixture():# pytest-asyncio 插件支持result = await some_async_function()return result@pytest.mark.asyncio
async def test_async(async_fixture):assert await async_fixture.process()

八、总结

Pytest 夹具的核心要点:

  1. 作用域管理:合理选择 scope 避免资源浪费
  2. 依赖注入:通过参数自动注入
  3. 资源清理:使用 yield 确保 teardown
  4. 代码复用:通过 conftest.py 共享夹具
  5. 灵活配置:支持参数化和工厂模式

掌握这些概念后,你可以编写更简洁、可维护的测试代码。

← 返回列表