登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  python教程

pytest 参数化 fixture怎样让不同输入复用同一组资源怎样完成一个可复用的最小示例

来源:17golang原创

时间:2026-11-10 05:35:32 271浏览 收藏

不同输入复用同一组资源,通常让测试函数负责输入参数化,让 fixture 的 scope 决定资源生命周期;如果变化的是资源配置,再使用 fixture params 或 indirect。参数化本身并不保证所有用例共享一个实例,资源复用也不应顺带共享上一例写入的数据。

假设要检查三种数值的入库逻辑,昂贵的是连接准备,变化的只是写入值。下面把模块共享连接与逐例事务隔离拆开,避免为减少初始化次数而引入测试顺序依赖。官方文档地址:https://docs.pytest.org/en/stable/how-to/fixtures.html。

1. 输入变化与资源变化要分开

先问变化发生在哪一层:若只是 value 不同,用 mark.parametrize;若同一套逻辑要面对不同后端配置,让参数进入资源 fixture。默认 fixture 是 function 作用域,每个用例独立构造;module 可以在同一模块内复用,session 则具有更大的共享范围。

变化对象建议位置
业务输入测试函数 parametrize
资源构造配置fixture params 或 indirect
需要复用的连接适当扩大连接 fixture scope
每例写入状态function fixture 初始化或回滚

扩大 scope 只定义生命周期,不自动让资源线程安全、事务安全,也不意味着参数化资源永远只创建一次。pytest 缓存当前 fixture 实例,参数切换可能导致同一 scope 内多次构造。

2. 给共享连接加上逐例回滚

保存为 test_shared_db.py。测试模块持有 db 连接;case_db 依赖连接,为测试函数提供逐例事务和事务回滚;参数输入仅由测试函数消费。这些依赖决定初始化边界,不能靠函数定义顺序代替。

模块资源与用例隔离边界
模块资源与用例隔离边界;静态关系说明图,非截图或运行证据。
import sqlite3
import pytest

@pytest.fixture(scope="module")
def db():
    # 同一模块复用一条内存连接,连接结束时关闭
    connection = sqlite3.connect(":memory:")
    try:
        connection.execute("CREATE TABLE sample (value INTEGER NOT NULL)")
        connection.commit()
        yield connection
    finally:
        connection.close()

@pytest.fixture
def case_db(db):
    # 每例独立事务;测试内禁止 commit,以保持回滚隔离
    db.execute("BEGIN")
    try:
        yield db
    finally:
        db.rollback()

@pytest.mark.parametrize("value", [2, 7, 11], ids=["small", "mid", "large"])
def test_insert(case_db, value):
    # 复用连接但不复用前一例写入的行,绑定参数避免拼接 SQL
    case_db.execute("INSERT INTO sample(value) VALUES (?)", (value,))
    count, total = case_db.execute(
        "SELECT COUNT(*), SUM(value) FROM sample"
    ).fetchone()
    assert count == 1
    assert total == value
# 在项目虚拟环境中安装 pytest,再执行这一份示例
python -m pip install pytest
python -m pytest -q --setup-show test_shared_db.py

本例未在本文环境运行。按代码语义,应收集三项输入,每项插入后只能看到一行;模块连接只供该模块中的相关测试复用,case_db 在每项结束后回滚。--setup-show 用于读者观察 setup/teardown 归属,不提供虚构的执行耗时。

这是一份受限的 SQLite 示例:测试和被测代码不能主动 commit,否则外部回滚无法撤销已提交的数据;不能把多线程或并发用例直接塞进同一连接。真实数据库应根据事务、连接池与迁移策略设计隔离,必要时改用独立库、schema 或数据命名空间。

yield 前创建资源失败时,yield 后清理段不会按正常路径执行;因此使用 try/finally 包围已经获得的连接。测试断言失败时,逐例 fixture 的 finally 仍负责回滚,模块 fixture 最后负责关闭连接。

3. 让资源配置参数进入 fixture

当 strict、relaxed 是不同构造配置时,可以通过 indirect 声明 backend 参数交给同名 fixture,再由 request.param 读取。这里用内存字典模拟资源对象,不代表真实连接或网络会话;backend fixture 用默认作用域确保每例独立容器。

配置参数与请求对象的依赖
配置参数与请求对象的依赖;静态关系说明图,非截图或运行证据。
import pytest

@pytest.fixture
def backend(request):
    # 参数是配置而非预先创建的资源,构造发生在 fixture 执行时
    return {"mode": request.param, "items": []}

@pytest.mark.parametrize(
    "backend", ["strict", "relaxed"], indirect=["backend"]
)
def test_backend(backend):
    # 默认 function 作用域让每例获得独立可变容器
    backend["items"].append("demo")
    assert backend["mode"] in {"strict", "relaxed"}
    assert backend["items"] == ["demo"]

可将第二段独立保存为 test_backend.py 再运行。它定义两项配置,重点是资源构造发生在 fixture 执行阶段,而非在参数列表里预先创建可变资源。不带 indirect 时,参数值会直接作为测试参数传入,并不会自动调用同名 fixture 完成配置。

如果多个测试都要遍历同一组配置,可以把配置移到 @pytest.fixture(params=[...]),让依赖它的测试自动扩展。不要同时对同一参数使用两套不一致的参数来源;多个独立参数维度还会形成组合,测试数量可能乘积增长。

遇到 ScopeMismatch 时,应检查宽作用域 fixture 是否依赖了更窄作用域资源。例如 module fixture 不能直接依赖 function fixture。对于并行测试,session 资源通常也不是跨所有工作进程只创建一次,部署外部共享资源要有明确的进程间协调。

测试数据为什么会串到下一例?

共享可变资源没有重置。扩大 scope 后,需增加逐例清理、回滚或独立数据边界,不能只靠参数化。

为什么测试拿到的是字符串配置?

检查是否声明 indirect,以及参数名与 fixture 名是否一致。直接参数化与间接参数化的输入位置不同。

scope 越大越好吗?

初始化成本与隔离难度要一起考虑。资源便宜、状态复杂时,function 通常更容易保持独立性。

间接参数化官方示例:https://docs.pytest.org/en/stable/example/parametrize.html。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>