fixture
pytest 中的 fixture 是一种强大的机制,用于提供测试函数所需的资源或上下文。它可以用于设置测试环境、准备数据等。以下是 fixture 的一些核心概念和使用场景
基本使用
示例1:使用与不使用fixture 标记
未标记fixture 方法的调用
python
# 未标记 fixture 方法的调用------函数名来调用
def fixture_01():
print("第一个 fixture 方法")
def test_fixture_01():
fixture_01()
print("第一个测试用例")
fixture 标记的方法调用
python
@pytest.fixture
def fixture_01():
print("第一个 fixture 方法")
def test_fixture_01(fixture_01):
print("第一个测试用例")
def test_fixture_02(fixture_01):
print("第二个测试用例")
def test_fixture_03(fixture_01):
print("第三个测试用例")

未标记fixture 方法的调用与 fixture标记的方法调用完全不一样,前者需要在方法体中调用,而后者可以将函数名作为参数进行调用。
测试脚本中存在很多重复的代码、公共的数据对象时,使用fixture 最为合适
示例2:访问列表页和详情页之前都需要执行登录操作
python
@pytest.fixture
def login():
print("登录")
def test_blogList(login):
print("测试博客列表页")
def test_blogDetail(login):
print("测试博客详情页")

通过使用 @pytest.fixture 装饰器来告诉pytest 一个特定函数是一个fixture,通过运行结果可见,在执行列表页和详情页之前都会先执行login 方法
fixture 嵌套
python
# fixture 嵌套
@pytest.fixture
def first():
print("first")
@pytest.fixture
def second(first):
print("second")
def test(second):
print("test")
# 其他嵌套用法
# 安排
@pytest.fixture
def first_entry():
return "a"
# 安排
@pytest.fixture
def order(first_entry):
return [first_entry]
def test_string(order):
# 行动
order.append("b")
# 断言
assert order == ["a", "b"]
测试不必局限于单个fixture ,它们可以依赖于你想要的任意数量的fixture,并且fixture 也可以使用其他fixture 。pytest 最伟大的优势之一是其极其灵活的fixture 系统,它允许我们将测试的复杂需求简化为更简单和有组织的函数,我们只需要每个函数描述它们所依赖的事物
请求多个fixture
python
class Fruit:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return self.name == other.name
@pytest.fixture
def my_fruit():
return Fruit("apple")
@pytest.fixture
def your_fruit(my_fruit):
return [my_fruit, Fruit("banana")]
# 你的水果里是否包含我的水果
def test_fruits(my_fruit,your_fruit):
assert my_fruit in your_fruit
测试和fixture 不仅限于一次请求单个fixture ,它们可以请求任意多个
yield fixture
当我们运行测试时,我们希望确保它们能够自我清理,以便它们不会干扰其他测试(同时也避免留下大量测试数据来膨胀系统)。pytest 中的fixture 提供了一个非常有用拆卸系统,它允许我们为每个fixture 定义具体的清理步骤
"yield" fixture 使用 yield 而不是 return 。有了这些fixture ,我们可以运行一些代码,并将对象返回给请求的fixture / test ,就像其他fixture 一样。唯一的不同是:
- return 被替换为 yield
- 该fixture 的任何拆卸代码放置在 yield 之后
一旦pytest 确定了 fixture 的线性顺序,它将运行每个 fixture 直到它返回或 yield,然后继续执行列表中的下一个 fixture 做同样的事情
测试完成后,pytest 将逆向遍历 fixture 列表,对于每个yield 的 fixture,运行yield 语句之后的代码
示例1:
python
# yield 用法
@pytest.fixture
def operator():
print("数据的初始化")
yield
print("数据的清理")
def test_01(operator):
print("测试方法执行")
@pytest.fixture
def operator():
print("数据的初始化")
yield 100
print("数据的清理")
def test_02(operator):
print(100 + operator)
assert operator == 100

示例2:创建文件句柄与关闭文件
python
# 创建文件句柄与关闭文件
@pytest.fixture
def file_read():
print("打开文件句柄")
fo = open("test.txt","r",encoding="utf-8")
yield fo # 相当于return
print("关闭打开的文件")
fo.close()
@pytest.fixture
def file_write():
print("打开文件句柄")
fo = open("test.txt","w",encoding="utf-8")
return fo
def test_file( file_read):
# 读取数据
r = file_read
str = r.read()
print(str)
# def test_file(file_read, file_write):
# # 写入数据
# w = file_write
# w.write("测试数据")
# w.close() # 写入后关闭文件句柄,以便读取
#
# # 读取数据
# r = file_read
# str = r.read()
# print(str)
带参数的fixture
python
pytest.fixture(scope='', params='', autouse='', ids='', name='')
参数详解:
--- scope 参数用于控制fixture 的作用范围,决定了fixture 的生命周期。可选值有
- function(默认):每个测试函数都会调用一次fixture
- class:在同一个测试类中共享这个fixture
- module:在同一个测试模块中共享这个fixture (一个文件中)
- session:整个测试会话中共享这个fixture
--- autouse 参数默认为False。如果设置为True,则每个测试函数都会自动调用该fixture,无需显式传入--- params 参数用于参数化fixture,支持列表传入。每个参数值都会使fixture 执行一次,类似于for 循环
--- ids 参数与 params 配合使用,为每个参数化实例指定可读的标识符(给参数取名字)
--- name 参数用于为fixture 显式设置一个名称。如果使用了name,则在测试函数中需要使用这个名称来引用fixture (给fixture取名字)
示例1: scope的使用
scope= "function"
python
# 示例1: scope的使用
# scope= "function"
import pytest
@pytest.fixture(scope="function")
def fixture_01():
print("初始化")
yield
print("清理")
class TestCase():
def test_01(self,fixture_01):
print("第一个测试用例")
def test_02(self,fixture_01):
print("第二个测试用例")

scope= "class"
python
# scope= "class"
import pytest
@pytest.fixture(scope="class")
def fixture_02():
print("初始化")
yield
print("清理")
class TestCase01():
def test_03(self,fixture_02):
print("第一个测试用例")
def test_04(self,fixture_02):
print("第二个测试用例")

结论:
- scope 默认为 function,这里的function 可以省略不写,当scope = "function" 时,每个测试函数都会调用一次 fixture。scope= "class" 时,在同一个测试类中,fixture 只会在类中的第一个测试函数开始前执行一次,并在类中的最后一个测试函数结束后执行清理
- 当 scope= "moudle"、scope= "session" 时可用于实现全局的前后置应用,这里需要多个文件的配合
conftest.py 和 @pytest.fixture 结合使用实现全局的前后置应用
@pytest.fixture 与 conftest.py 文件结合使用,可以实现在多个测试模块(.py)文件中共享前后置操作,这种结合的方式使得可以在整个测试项目中定义和维护通用的前后置逻辑,使测试代码更加模块化和可维护
规则:
- conftest.py 是一个单独存放的夹具配置文件,名称是固定的不能修改
- 你可以在项目中的不同目录下创建多个 conftest.py 文件,每个 conftest.py 文件都会对其所在目前及其子目录下的测试模块生效
- 在不同模块的测试中需要用到 conftest.py 的前后置功能时,不需要做任何的import 导入操作
- 作用:可以在不同的 .py 文件中使用同一个 fixture 函数
示例2:scope = "moudle"、scope = "session" 实现全局的前后置应用
python
import pytest
@pytest.fixture(scope="module",autouse=True)
def fixture_01():
print("初始化")
yield
print("清理")
test_case_01.py
python
def test_case01():
print("单独的测试用例01")
class TestCase01():
def test_01(self):
print("第一个测试用例")
def test_02(self):
print("第二个测试用例")
test_case_02.py
python
def test_case02():
print("单独的测试用例01")
class TestCase02():
def test_01(self):
print("第一个测试用例")
def test_02(self):
print("第二个测试用例")

scope = "session"
python
import pytest
@pytest.fixture(scope="session",autouse=True)
def fixture_01():
print("初始化")
yield
print("清理")
test_case_01.py
python
def test_case01():
print("单独的测试用例01")
class TestCase01():
def test_01(self):
print("第一个测试用例")
def test_02(self):
print("第二个测试用例")
test_case_02.py
python
def test_case02():
print("单独的测试用例02")
class TestCase02():
def test_01(self):
print("第一个测试用例")
def test_02(self):
print("第二个测试用例")

示例3:autouse的使用
python
import pytest
@pytest.fixture(scope="class", autouse=True)
def fixture_01():
print("初始化")
yield
print("清理")
class TestClass():
def test_01(self):
print("第一个测试用例")
def test_02(self):
print("第二个测试用例")

autouse 默认为False,即当前的fixture 需要手动显示调用,在该案例之前我们默认使用的都是 autouse= False
当autouse = True 时,fixture 会在所有测试函数执行之前自动调用,无论这些测试函数是否显式引用了该fixture
示例4:通过params 实现参数化
python
# 定义一个参数化的fixture
@pytest.fixture(params=["a","b"])
def data_provider(request):
return request.param
# 定义一个测试函数,它依赖于上面的参数化 fixture
def test_data(data_provider):
assert data_provider != None
print(f"Testing with data provider: {data_provider}")

之前的文章,我已经提到过 pytest中的 @pytest.mark.parametrize 实现参数化,通过fixture 也可以实现参数化,那么到底哪种更好呢?
如果测试场景主要涉及简单的参数传递,且不需要复杂的资源管理,建议使用parametrize,因为它更简单直接;如果测试需要动态加载外部数据,或者需要管理复杂的测试资源(如数据库连接、文件操作等),建议使用fixture,在某项情况下,也可以结合使用 parametrize 和 fixture ,以充分利用二者的优点。
总结来说,parametrize 更适合简单场景,而 fixture 更适合需要动态数据和资源管理的复杂场景