详解 pytest fixture 作用域

作者:c_chun 发布时间: 2025-09-25 阅读量:4 评论数:0

在自动化测试中,我们常常需要处理一些重复操作——比如每次执行测试用例前都要登录系统。如果每个用例都重复执行登录,不仅会浪费时间,还可能影响测试效率。最近在优化一个收货地址测试场景时,我通过调整 pytest fixture 的作用域,轻松解决了“重复登录”的问题。今天就以这个实际案例为例,聊聊 pytest fixture 作用域的用法和最佳实践。

一、问题场景:为什么我的测试在重复登录?

先看一个真实场景:在测试“新增收货地址”功能时,我写了一个测试类 TestAddAddress,包含 4 个测试用例(成功添加、姓名为空失败、电话为空失败、地址详情为空失败)。为了保证测试独立,我用 fixture 封装了登录逻辑,代码大概长这样:

import pytest
from keywords import Keywords

# 登录 fixture,作用域默认是 function
@pytest.fixture(scope="function")
def logged_in_keywords(base_url, logger):
    # 初始化关键词对象
    keywords = Keywords(base_url, logger)
    # 执行登录
    login_response = keywords.login("user123", "user123")
    assert login_response.get("errno") == 0, "登录失败"
    # 返回登录后的关键词对象(携带 token)
    yield keywords

# 测试类
class TestAddAddress:
    # 4 个参数化用例,测试不同场景
    @pytest.mark.parametrize("case_id, request_data, expected_results", test_data)
    def test_add_address(self, logged_in_keywords, logger, case_id, request_data, expected_results):
        # 调用新增地址接口(依赖登录后的 token)
        response = logged_in_keywords.add_address(**request_data)
        # 断言结果
        assert response.get("errno") == expected_results["errno"]

运行后发现,日志里竟然有 4 次登录记录!每个用例执行前都会重新登录一次:

2025-09-25 16:26:21 - [INFO] - [test_add_address[add_address_success]] - --------- 关键字: login [开始] ---------
2025-09-25 16:26:21 - [INFO] - [test_add_address[add_address_fail_null_name]] - --------- 关键字: login [开始] ---------
2025-09-25 16:26:22 - [INFO] - [test_add_address[add_address_fail_null_tel]] - --------- 关键字: login [开始] ---------
2025-09-25 16:26:22 - [INFO] - [test_add_address[add_address_fail_null_addressdetail]] - --------- 关键字: login [开始] ---------

这显然不合理:同一批测试用例属于同一个用户,完全可以共享一次登录状态。问题出在哪?—— 答案是 fixture 的作用域(scope)

二、什么是 fixture 作用域?

pytest 的 fixture 作用域决定了 fixture 被执行的频率,即“在什么范围内,fixture 只需要初始化一次”。默认情况下,fixture 的作用域是 function(每个测试用例执行一次),这也是上面例子中“4 个用例执行 4 次登录”的原因。

pytest 提供了 5 种作用域,从小到大依次是:

作用域

含义

执行频率

function

函数级(默认)

每个测试用例执行前初始化一次

class

类级

每个测试类执行前初始化一次

module

模块级(一个 .py 文件为一个模块)

每个模块执行前初始化一次

package

包级(一个文件夹为一个包)

每个包执行前初始化一次

session

会话级(整个测试过程为一个会话)

整个测试会话只初始化一次

三、作用域实战:从 4 次登录到 1 次登录

回到刚才的问题:4 个用例属于同一个测试类 TestAddAddress,且共享同一个用户的登录状态。此时最适合的作用域是 class——让 fixture 在整个测试类执行前只登录一次。

优化步骤:修改 fixture 作用域为 class

只需将 fixture 的 scope 参数从默认的 function 改为 class

# 优化后的 fixture:作用域改为 class
@pytest.fixture(scope="class")  # 关键修改
def logged_in_keywords(base_url, logger):
    keywords = Keywords(base_url, logger)
    login_response = keywords.login("user123", "user123")
    assert login_response.get("errno") == 0, "登录失败"
    yield keywords  # 整个类的用例共享此对象

再次运行测试,日志中只有 1 次登录 了:

2025-09-25 17:00:01 - [INFO] - [TestAddAddress] - --------- 关键字: login [开始] ---------
# 后续 4 个用例直接使用登录后的状态,不再重复登录

这是因为 scope="class" 让 fixture 在 TestAddAddress 类初始化时执行一次,生成的 keywords 对象(包含登录后的 token)会被类中所有用例共享,避免了重复登录。

四、不同作用域的适用场景

理解作用域的核心是:根据“资源的共享范围”选择合适的作用域。以下是各作用域的典型适用场景:

1. function:每个用例独立初始化

适用场景:需要隔离的资源(如每个用例需要全新的测试数据、独立的数据库连接)。

例子:测试“用户注册”功能,每个用例需要不同的手机号,此时注册前的“清空用户数据”操作适合用 function 作用域,确保用例间不干扰。

@pytest.fixture(scope="function")
def clean_user_data(logger):
    # 每个用例执行前清空测试用户数据
    logger.info("清空用户数据")
    db.execute("DELETE FROM user WHERE username LIKE 'test_%'")
    yield

2. class:同一类用例共享资源

适用场景:同一类用例属于同一功能模块,可共享前置条件(如登录状态、基础配置)。

例子:本文中的“新增收货地址”测试类,所有用例都依赖同一用户的登录状态,用 class 作用域最合理。

3. module:模块内共享资源

适用场景:一个模块(.py 文件)内的多个测试类/函数共享资源(如数据库连接、接口基础配置)。

例子:一个 test_order.py 模块包含订单创建、支付、取消等测试类,可共享“初始化订单表”的 fixture:

@pytest.fixture(scope="module")
def init_order_table():
    # 模块执行前初始化订单表(创建测试数据)
    db.execute("INSERT INTO order (id, status) VALUES (1001, 'UNPAID')")
    yield
    # 模块执行后清理
    db.execute("DELETE FROM order WHERE id=1001")

4. session:全局共享资源

适用场景:整个测试过程中只需要初始化一次的资源(如启动测试服务、连接数据库)。

例子:UI 自动化测试中,浏览器驱动的初始化适合用 session 作用域,避免每次用例都启动新浏览器:

@pytest.fixture(scope="session")
def browser():
    # 整个测试会话只启动一次浏览器
    driver = webdriver.Chrome()
    yield driver
    # 会话结束后关闭浏览器
    driver.quit()

五、作用域使用注意事项

  1. 作用域的“继承”与“限制”
    如果 fixture A 依赖 fixture B,A 的作用域不能比 B 小。例如:class 级的 fixture 不能依赖 function 级的 fixture(否则会导致 B 被重复执行)。

  2. 共享资源的“污染”风险
    作用域越大,资源被共享的范围越广,需注意用例对资源的“修改”是否会影响其他用例。例如:session 级的登录状态如果被某用例登出,会导致后续用例失败。

  3. 优先选择“最小必要”作用域
    不要盲目使用大作用域(如 session),应根据实际需求选择最小可行的作用域。例如:只有当所有用例都能共享同一资源时,才用 session

六、总结

fixture 作用域是 pytest 中提升测试效率的关键特性。通过合理设置作用域,我们可以避免重复操作(如多次登录),同时保证测试的独立性和稳定性。

回到开头的例子,仅仅修改一个参数 scope="class",就将登录次数从 4 次减少到 1 次,测试效率提升明显。希望通过这个实际案例,你能更清晰地理解 fixture 作用域的用法——记住:作用域的本质是“资源共享的范围”,选择合适的范围,让测试既高效又可靠

评论