📝 面试求职: 「面试试题小程序」 ,内容涵盖 测试基础、Linux操作系统、MySQL数据库、Web功能测试、接口测试、APPium移动端测试、Python知识、Selenium自动化测试相关、性能测试、性能测试、计算机网络知识、Jmeter、HR面试,命中率杠杠的。(大家刷起来…)
📝 职场经验干货:
大家好!我们今天来学习 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.fixture
def 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.fixture
def 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 client
print(f"\n【Fixture Teardown】正在清理角色 {role} 的客户端")
client.logout() # 假设有登出操作
class MockAPIClient:
def __init__(self, role):
self.role = role
self.logged_in = True
print(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 = False
print(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=[0, 1, 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 pytest
import 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 default
def 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 == 1
def 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%免费】