一、介绍
作用
fixture主要有两个作用:
- 复用测试数据和环境,可以减少重复的代码;
- 可以在测试用例运行前和运行后设置和清理资源,避免对测试结果产生影响,同时也可以提高测试用例的运行效率。
优势
pytest框架的fixture测试夹具就相当于unittest框架的setup、teardown,但相对之下它的功能更加强大和灵活。
- 命名方式灵活,不局限于 setup 和teardown 这几个命名。
- conftest.py 配置里可以实现数据共享,不需要 import 就能自动找到fixture。
- scope=“session” 可以实现多个.py 跨文件共享前置。(fixture为module时,在当前.py脚本里面所有用例开始前只执行一次。fixture为session级别是可以跨.py模块调用的,也就是当我们有多个.py文件的用例的时候,如果多个用例只需调用一次fixture,那就可以设置为scope=“session”,并且写到conftest.py文件里。)
- 可以实现unittest不能实现的功能,比如unittest中的测试用例和测试用例之间是无法传递参数和数据的,但是fixture却可以解决这个问题
与传统 setup/teardown 的对比
特性 | fixture | setup/teardown |
---|---|---|
代码复用 | 高(通过函数和参数化) | 低(需继承或重复代码) |
依赖关系 | 显式(通过参数声明) | 隐式(通过类结构) |
执行顺序 | 灵活(支持复杂依赖图) | 固定(setup → test → teardown) |
作用域控制 | 支持多级作用域(function/module/session) | 仅支持类或方法级别 |
参数化测试 | 原生支持 | 需要额外代码 |
二、第一个fixture
#!/usr/bin/env python
# encoding: utf-8
'''
@desc : 第一次使用fixture简单示例
'''
import pytest
# 用例预置数据
@pytest.fixture
def first_fix():
return 'hello '
# 测试用例
def test_01(first_fix):
# 用例步骤
first_fix +='fixture!'
print(first_fix)
if __name__ == '__main__':
pytest.main(['-s', 'test_first_fix.py'])
控制台输出:
对比:
# 旧方法,不适用pytest的实现方法
def first_fix():
return 'hello '
# 测试用例
def test_01(first_fix):
# 用例步骤
first_fix +='fixture!'
print(first_fix)
ffix = first_fix()
test_01(ffix)
控制台:
hello fixture!
三、fixture互相调用
#!/usr/bin/env python
# encoding: utf-8
'''
@Author : 草木零
@Software: PyCharm
@File : test_fix_callEachOther.py
@Time : 2023/8/22 18:39
@desc : fixture互相调用
'''
import pytest
# @pytest.fixture()不传递参数时,写法效果相当于@pytest.fixture
@pytest.fixture()
def fix01():
return {'name': 'Erin', 'age': 26}
@pytest.fixture
def fix02(fix01):
fix01['city'] = 'Guangzhou'
return fix01
@pytest.fixture
def fix03(fix01):
fix01 = [fix01]
fix01.append({'name': 'John', 'age': 13})
return fix01
def test_case01(fix02, fix03):
print(fix02)
print(fix03)
# def test_case02(fix03):
# print(fix03)
if __name__ == '__main__':
pytest.main(['-sv', 'test_fix_callEachOther.py'])
控制台输出:
不使用pytest的旧方法实现对比:
def fix01():
return {'name': 'Erin', 'age': 26}
def fix02(fix01):
fix01['city'] = 'Guangzhou'
return fix01
def test_call_each_other(fix02):
print(fix02)
fix1 = fix01()
fix2 = fix02(fix1)
test_call_each_other(fix2)
控制台输出:
四、装饰器@pytest.mark.usefixtures()
叠加使用usefixtures:如果一个方法或者一个class用例想要同时调用多个fixture,可以使用pytest.mark.usefixtures()
进行叠加。注意叠加顺序,先执行的放底层,后执行的放上层。见下例。
在类上使用usefixtures,类中的用例就不要再重复使用了
下例展示了三种使用,
用于函数
用于类
@pytest.mark.usefixtures('fix1','fix2')
用于类叠加使用
@pytest.mark.usefixtures(‘fix1’)
@pytest.mark.usefixtures(‘fix2’)
#!/usr/bin/env python
# encoding: utf-8
'''
@Author : 草木零
@Software: PyCharm
@File : test_usefixtures.py
@Time : 2023/8/22 19:12
@desc : 使用装饰器@pytest.mark.usefixtures()修饰需要运行的用例
'''
import pytest
@pytest.fixture
def fix1():
print('fixture1的打印值')
@pytest.fixture
def fix2():
print('fixture2的打印值')
# 函数使用usefixtures
@pytest.mark.usefixtures('fix1','fix2')
def test_func():
print('函数usefixtures的使用方法')
# class使用fixtures
# 这种直接在类上统一的使用fixtrue,类当中的用例就不用再重复使用了
@pytest.mark.usefixtures('fix1','fix2')
class Test_class01:
def test_01(self):
print('类Test_class01里的测试用例1')
def test_02(self):
print('类Test_class01里的测试用例2')
# class使用fixtures
# 1.注意:这种方法,在fixture+conftest.py的组合形式下,是无法被引用的
# 2. 这种直接在类上统一的使用fix,类当中的用例就不用再重复使用了
# 如果一个方法或者一个class用例想要同时调用多个fixture,可以使用@pytest.mark.usefixtures()进行叠加。
# 注意叠加顺序,先执行的放底层,后执行的放上层。
# 比如这里,fix2放在下层,所以先调fix2
@pytest.mark.usefixtures('fix1')
@pytest.mark.usefixtures('fix2')
class Test_class02:
def test_01(self):
print('类Test_class02里的测试用例1')
def test_02(self):
print('类Test_class02里的测试用例2')
if __name__ == '__main__':
pytest.main(['-s', 'test_usefixtures.py'])
控制台输出:
五、类使用fixture的两种方式
装饰器@pytest.mark.usefixtures()
例子见上面四
通过类属性引用 fixture:pytest.lazy_fixture()
若fixture返回数据且测试类需要使用
@pytest.fixture
def user_data():
return {"name": "Alice", "age": 30}
# 方法一:通过测试方法的参数获取
# class TestUser:
# @pytest.mark.usefixtures("user_data") # 确保 fixture 被执行
# def test_user_age(self, user_data): # 方法参数获取返回值
# assert user_data["age"] == 30
# 方法二:通过 pytest.lazy_fixture(推荐)
class TestUser:
# 声明类级别的 fixture 依赖,并可在方法中使用
user_data = pytest.lazy_fixture("user_data")
def test_user_age(self):
assert self.user_data["age"] == 30 # 通过 self 访问 fixture 返回值
六、函数/方法使用多个fixture
执行顺序由装饰器或参数列表的顺序决定
#!/usr/bin/env python
# encoding: utf-8
'''
@Author : 草木零
@Software: PyCharm
@File : test_multiFixtures.py
@Time : 2023/8/22 20:11
@desc : 使用多个fixture
'''
import pytest
@pytest.fixture
def fix1():
return ('葡萄', '芒果')
@pytest.fixture
def fix2():
print('这是fix2')
def test_multiFix(fix1, fix2):
print(fix1[0], fix1[1])
if __name__ == '__main__':
pytest.main(['-s', 'test_multiFixtures.py'])
控制台
注意上面的输出,先输出fix2的内容,尽管顺序是(fix1, fix2)
输出顺序由 pytest 的 fixture 初始化算法决定:
- 无依赖的 fixture 按参数列表顺序初始化(通常情况)
- 有副作用(如打印)的 fixture 会提前暴露执行痕迹
最佳实践:
- 避免在 fixture 中使用有副作用的操作(如打印)
- 通过显式依赖链(fixture 依赖其他 fixture)控制顺序
- 使用 pytest-ordering 插件精确控制测试执行顺序
执行顺序与参数位置无关:
- 参数列表中的顺序不保证 fixture 实际执行顺序
- pytest 会优化依赖图以最小化初始化开销
使用fixture的方式选择
两种方式的选择取决于是否需要获取 fixture 的返回值:
@pytest.mark.usefixtures
:适合 “执行后无需结果” 的场景(如环境初始化)。test_case(fix)
:适合 “需要使用 fixture 数据” 的场景(如提供测试参数)。
import pytest
@pytest.fixture(params=[1, 2])
def numbers(request):
return request.param
# 方式1:通过装饰器
@pytest.mark.usefixtures("numbers")
def test_with_decorator():
# 无法直接访问 numbers 的值,但测试会执行两次(参数为 1 和 2)
pass
# 方式2:通过参数
def test_with_param(numbers):
assert numbers in [1, 2] # 可直接使用参数值
if __name__ == '__main__':
pytest.main(['-sv', 'test_fixture_contrast.py'])
控制台输出:
七、fixture之params参数化
@pytest.fixture(params=list)
,params参数接收列表类型数据,fixture函的 params 请求参数数量决定fixture函数执行的次数。
request.param
:request是pytest的内置fixture,主要用于传递参数
#!/usr/bin/env python
# encoding: utf-8
'''
@Author : 草木零
@Software: PyCharm
@File : test_fixture_params.py
@Time : 2023/8/22 23:58
@desc : fixture之params参数化,@pytest.fixture(params=list) #params参数接收列表类型数据
'''
import pytest
def read_list():
return ['芒果', '榴莲', '樱桃']
@pytest.fixture(params=read_list()) # params参数接收列表类型数据
def fix(request):
# request是pytest的内置fixture,主要用于传递参数
print(request.param, type(request.param))
fruit = request.param
return fruit
def test_params(fix):
print('params接收的列表有多少个元素,本用例就执行多少次,这次接收的水果是{}'.format(fix))
if __name__ == '__main__':
pytest.main(['-s', 'test_fixture_params.py'])
控制台输出: