掌握了它,你就掌握了 Pytest 的精髓!测试效率暴涨!

📝 面试求职: 「面试试题小程序」 ,内容涵盖 测试基础、Linux操作系统、MySQL数据库、Web功能测试、接口测试、APPium移动端测试、Python知识、Selenium自动化测试相关、性能测试、性能测试、计算机网络知识、Jmeter、HR面试,命中率杠杠的。(大家刷起来…)

📝 职场经验干货:

软件测试工程师简历上如何编写个人信息(一周8个面试)

软件测试工程师简历上如何编写专业技能(一周8个面试)

软件测试工程师简历上如何编写项目经验(一周8个面试)

软件测试工程师简历上如何编写个人荣誉(一周8个面试)

软件测试行情分享(这些都不了解就别贸然冲了.)

软件测试面试重点,搞清楚这些轻松拿到年薪30W+

软件测试面试刷题小程序免费使用(永久使用)


大家好!我们今天来学习 Python 测试框架中的最具特色的功能之一:Fixture。

可以说,掌握了 Fixture,你就掌握了 Pytest 的精髓。它不仅能让你的测试代码更简洁、更优雅、更易于维护,还能极大地提升测试的复用性和灵活性。

作者为大家介绍了Fixture 是什么?为什么我们需要它?如何快速上手:第一个 Fixture 与基本用法及作用域 (Scope):控制 Fixture 的生命周期

接下来,本文将带你系统性地讲解资源管理、数据驱动及自动使用 Fixture的便利性与风险

资源管理:Setup/Teardown: yield

我们在第一个例子中已经看到了 yield 的使用。这是 Pytest Fixture 实现 Setup 和 Teardown 的推荐方式。

    import pytest# 假设 connect_to_real_or_mock_db 和 MockDbConnection 已定义 (如上个例子) @pytest.fixturedef db_connection():print("\n【Setup】正在连接数据库...")conn = connect_to_real_or_mock_db() # 假设这是一个连接函数yield conn # 将连接对象提供给测试,并在此暂停print("\n【Teardown】正在断开数据库连接...")conn.close() # yield 之后执行清理 def test_db_query(db_connection):print("【测试】正在执行查询...")result = db_connection.execute("SELECT  FROM users")assert result is not None

    yield 方式的优点:

    • 代码集中:Setup 和 Teardown 逻辑写在同一个函数内,结构清晰。

    • 状态共享:yield 前后的代码可以共享局部变量(如上面例子中的 conn)。

    • 异常处理:如果 Setup 代码( yield 之前)或测试函数本身抛出异常,Teardown 代码( yield 之后)仍然会执行,确保资源被释放。

    另一种方式: request.addfinalizer

    在 yield Fixture 出现之前,

    通常使用 request.addfinalizer 来注册清理函数。

      import pytest# 假设 connect_to_real_or_mock_db 和 MockDbConnection 已定义 @pytest.fixturedef legacy_db_connection(request):print("\n【Setup】正在连接数据库...")conn = connect_to_real_or_mock_db() def fin():print("\n【Teardown】正在断开数据库连接...")conn.close() request.addfinalizer(fin) # 注册清理函数return conn # 使用 return 返回值 def test_legacy_db_query(legacy_db_connection):print("【测试】正在执行查询...")result = legacy_db_connection.execute("SELECT  FROM products")assert result is not None

      虽然 addfinalizer 仍然有效,但 yield 方式是更简洁的上下文管理器风格,是目前推荐的首选。

      参数化 Fixture:让 Fixture 更强大

      有时,我们希望同一个 Fixture 能够根据不同的参数提供不同的 Setup 或数据。例如,测试一个需要不同用户角色的 API。

      可以使用 @pytest.fixture 的 params 参数,并结合内置的 request Fixture 来实现。

        import pytest # 参数化的 Fixture,模拟不同用户角色@pytest.fixture(params=["guest""user""admin"], scope="function")def user_client(request):role = request.param # 获取当前参数值print(f"\n【Fixture Setup】正在为角色创建客户端:{role}")# 模拟根据角色创建不同的客户端或设置client = MockAPIClient(role=role)yield clientprint(f"\n【Fixture Teardown】正在清理角色 {role} 的客户端")client.logout() # 假设有登出操作 class MockAPIClient:def __init__(self, role):self.role = roleself.logged_in = Trueprint(f"  客户端已初始化,角色为 '{self.role}'") def get_data(self):if self.role == "guest":return {"data""公共数据"}elif self.role == "user":return {"data""用户专属数据"}elif self.role == "admin":return {"data""所有系统数据"}return None def perform_admin_action(self):if self.role != "admin":raise PermissionError("需要管理员权限")print("  正在执行管理员操作...")return {"status""成功"} def logout(self):self.logged_in = Falseprint(f"  角色 '{self.role}' 的客户端已登出")  # 使用参数化 Fixture 的测试函数def test_api_data_access(user_client):print(f"【测试】正在测试角色 {user_client.role} 的数据访问权限")data = user_client.get_data()if user_client.role == "guest":assert data == {"data""公共数据"}elif user_client.role == "user":assert data == {"data""用户专属数据"}elif user_client.role == "admin":assert data == {"data""所有系统数据"} def test_admin_action_permission(user_client):print(f"【测试】正在测试角色 {user_client.role} 的管理员操作权限")if user_client.role == "admin":result = user_client.perform_admin_action()assert result == {"status""成功"}else:with pytest.raises(PermissionError):user_client.perform_admin_action()print(f"  为角色 '{user_client.role}' 正确引发了 PermissionError")

        运行 pytest -s -v:

        你会看到 

        test_api_data_access 

        和 test_admin_action_permission 这两个测试函数,都分别针对 params 中定义的 "guest", "user", "admin" 三种角色各执行了一次,总共执行了 6 次测试。

        每次执行时,

        user_client Fixture 都会根据 

        request.param 的值进行相应的 Setup 和 Teardown。

        • params 和 ids:

        你还可以提供 ids 参数,为每个参数值生成更友好的测试 ID:

          @pytest.fixture(params=[01, pytest.param(2, marks=pytest.mark.skip)],ids=["零""一""跳过的二"])def number_fixture(request):print(f"\n【参数化 Fixture】提供参数:{request.param}")return request.param def test_using_number(number_fixture):print(f"【测试】使用数字:{number_fixture}")assert isinstance(number_fixture, int)

          这会生成如 test_using_number[零]

          test_using_number[一] 这样的测试 ID,并且跳过的二对应的测试会被跳过。

          • 参数化 Fixture 与 

            @pytest.mark.parametrize 的区别:

          • @pytest.mark.parametrize 是直接作用于测试函数,为其提供多组输入参数。

          • 参数化 Fixture 是让 Fixture 本身可以产生不同的输出(通常是 Setup 结果),使用该 Fixture 的测试函数会针对 Fixture 的每个参数化实例运行一次。

          • 它们可以组合使用,实现更复杂的测试矩阵。

          自动使用的 Fixture (autouse):

          便利性与风险

          默认情况下,测试函数需要显式地在其参数列表中声明它所依赖的 Fixture。

          但有时,我们希望某个 Fixture 对某个范围内的所有测试都自动生效,而无需在每个测试函数中都写一遍参数。这就是 autouse=True 的用途。

            import pytestimport time

            一个自动使用的 Session Fixture,例如用于全局日志配置

              @pytest.fixture(scope="session", autouse=True)def setup_global_logging():    print("\n【自动 Session Setup】正在配置全局日志...")    # configure_logging() # 假设这里配置日志    yield    print("\n【自动 Session Teardown】正在关闭日志系统。")

              一个自动使用的 Function Fixture,例如每次测试前重置某个状态

                _test_counter = 0@pytest.fixture(autouse=True# scope is function by defaultdef reset_counter_before_each_test():    global _test_counter    print(f"\n【自动 Function Setup】正在重置计数器。当前值:{_test_counter}")    _test_counter = 0    yield    # yield 后的清理代码会在测试函数执行后运行    print(f"【自动 Function Teardown】测试完成。计数器现在是:{_test_counter}")
                  def test_increment_counter_once():    global _test_counter    print("【测试】计数器增加一。")    _test_counter += 1    assert _test_counter == 1def test_increment_counter_twice():    global _test_counter    print("【测试】计数器增加二。")    _test_counter += 1    _test_counter += 1    assert _test_counter == 2

                  这个测试函数没有显式请求任何 Fixture,但 autouse Fixture 仍然会执行

                    def test_simple_assertion():    print("【测试】运行一个简单的断言。")    assert True

                    运行 pytest -s -v:

                    你会看到:

                    • setup_global_logging 在整个会话开始和结束时执行。

                    • reset_counter_before_each_test 

                      在 test_increment_counter_once,

                      test_increment_counter_twice, 

                      甚至 test_simple_assertion 这三个测试函数执行之前和之后都执行了。

                    autouse 的优点:

                    方便:对于必须在每个测试(或特定范围内所有测试)之前运行的通用设置(如日志、数据库事务回滚、模拟 Patcher 启动/停止)非常方便。

                    autouse 的风险和缺点:

                    • 隐式依赖:

                      测试函数的依赖关系不再明确地体现在参数列表中,降低了代码的可读性和可维护性。

                      当测试失败时,可能难以追踪是哪个 autouse Fixture 导致的问题。

                    • 过度使用:

                      滥用 autouse 会使测试环境变得复杂和不可预测。

                    • 作用域陷阱:

                      autouse Fixture 只在其定义的作用域内自动激活。

                      例如,一个 autouse=True, scope="class" 的 Fixture 只会对该类中的测试方法自动生效。

                    使用建议:

                    • 谨慎使用 autouse=True。

                    • 优先考虑显式 Fixture 注入,因为它更清晰。

                    • 仅对那些真正具有全局性、不言而喻且不直接影响测试逻辑本身的 Setup/Teardown 使用 autouse

                      (例如,日志配置、全局 Mock 启动/停止、数据库事务管理)。

                    • 如果一个 Fixture 提供了测试需要的数据或对象,绝对不要使用 autouse=True,因为它需要被注入到测试函数中才能使用。

                      autouse Fixture 通常不 yield 或 return 测试所需的值(虽然技术上可以,但不推荐)。

                    最后: 下方这份完整的软件测试视频教程已经整理上传完成,需要的朋友们可以自行领取【保证100%免费】

                    ​​

                    ​​​​

                    评论
                    添加红包

                    请填写红包祝福语或标题

                    红包个数最小为10个

                    红包金额最低5元

                    当前余额3.43前往充值 >
                    需支付:10.00
                    成就一亿技术人!
                    领取后你会自动成为博主和红包主的粉丝 规则
                    hope_wisdom
                    发出的红包
                    实付
                    使用余额支付
                    点击重新获取
                    扫码支付
                    钱包余额 0

                    抵扣说明:

                    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
                    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

                    余额充值