
Pytest 默认对相同参数的 fixture 实例进行缓存,导致 @pytest.mark.parametrize 中重复参数仅触发一次 fixture 执行;本文提供一种通过返回闭包函数的方式,绕过缓存机制,确保每次调用都真实执行逻辑。
pytest 默认对相同参数的 fixture 实例进行缓存,导致 @pytest.mark.parametrize 中重复参数仅触发一次 fixture 执行;本文提供一种通过返回闭包函数的方式,绕过缓存机制,确保每次调用都真实执行逻辑。
在 Pytest 中,fixture 的执行行为受其作用域(scope)和参数化方式共同影响。当使用 indirect=True 配合 @pytest.mark.parametrize 为 fixture 传参时,Pytest 会将参数元组作为缓存键——即使你写了两次 {"delivery_date": "12-12-2024"},Pytest 也认为这是“同一输入”,因此复用已生成的 fixture 实例,而非重新执行。这正是你遇到的问题根源:fixture 只插入一条订单记录,而非预期的两条。
✅ 正确解法:Fixture 返回可调用函数(工厂模式)
与其让 fixture 直接执行副作用(如写入 DynamoDB),不如让它返回一个按需执行的工厂函数。这样既保持 fixture 的生命周期管理(如依赖 dynamodb_client 和 cleanup_orders),又彻底规避参数缓存限制:
import uuid
import pytest
@pytest.fixture(scope="function")
def function_order(request, dynamodb_client, cleanup_orders):
def _get_order_record(delivery_date="01-01-2022"):
order_id = f"ORDER-{uuid.uuid4()}"
record = {
"order_id": {"S": order_id},
"delivery_date": {"S": delivery_date},
}
dynamodb_client.put_item(
TableName="orders",
Item=record,
)
cleanup_orders.append(order_id)
return {"order_id": order_id, "record": record}
return _get_order_record
? 注意:
_get_order_record是闭包,能访问 fixture 内部的dynamodb_client和cleanup_orders,无需额外注入。
对应的测试用例也同步重构,显式调用两次:
def test_post_v1_orders_exceeds_order_limit_returns_400(
request_helper, function_order
):
delivery_date = "12-12-2024"
# 显式插入两条同日期订单 → 触发限流逻辑
function_order(delivery_date)
function_order(delivery_date)
# 第三次提交应返回 400
response = request_helper.post(
"/v1/orders",
body=order.order_record(delivery_date=delivery_date),
)
assert response.status_code == 400
assert response.json().get("error") == f"Order limit exceeded for date: {delivery_date}. Max orders: 2"
⚠️ 关键注意事项
-
不要混用
indirect=True与函数式调用:一旦 fixture 返回函数,就不再适用@pytest.mark.parametrize(..., indirect=True),否则会引发FixtureLookupError或逻辑错乱。 -
清理逻辑仍有效:
cleanup_orders是 list 类型 fixture,在测试结束时仍可由其他 fixture(如 session-scoped 清理器)统一处理,无需修改。 -
UUID 确保唯一性:每次调用
_get_order_record()都生成新order_id,避免 DynamoDB 主键冲突。 -
作用域选择合理:
scope="function"保证每个测试函数独享隔离环境,符合集成测试需求。
✅ 总结
Pytest 的 fixture 缓存机制是性能优化特性,但在需要“多次执行相同逻辑”的场景(如构造多条测试数据)下,需主动规避。将 fixture 设计为工厂函数(返回 callable)是最简洁、可维护且符合 Pytest 哲学的解决方案——它既利用了 fixture 的依赖注入和生命周期管理能力,又赋予测试用例完全的控制权。此模式广泛应用于数据库预置、Mock 初始化、临时文件创建等场景。










