pytest fixture基础大全详解

发布于:2025-07-03 ⋅ 阅读:(26) ⋅ 点赞:(0)

一、介绍

作用

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,类中的用例就不要再重复使用了

下例展示了三种使用,

  1. 用于函数

  2. 用于类@pytest.mark.usefixtures('fix1','fix2')

  3. 用于类叠加使用

    @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'])

控制台输出:
在这里插入图片描述