
pytest 框架还提供了 Fixture 夹具,使我们能够定义一个通用的设置步骤,该步骤可以反复使用,就像使用普通函数一样。不同的测试用例可以请求同一个 Fixture 进行同样的步骤处理,然后 pytest 会根据该 Fixture 为每个测试测试用例提供各自的结果数据。
这对于确保测试用例不会相互影响非常有用,我们可以使用此系统确保每个测试都获得自己的新数据,并从干净的状态开始,以便提供一致、可重复的结果。比如用户模块的各个测试用例,都可以被查询用户信息的 Fixture 夹具所装饰,或者运行每个测试用例前清理数据库数据。
测试用例使用 Fixture
# \test\test_mod1.py
import pytest
@pytest.fixture
def fixture_01():
return [1, 2, 3]
def test_01(fixture_01):
assert fixture_01 == [1, 2, 3], "测试失败"
def test_02(fixture_01):
assert fixture_01 == [1, 2], "测试失败"
运行结果:
===================================================================== test session starts ======================================================================
collected 4 items
Config::test_01 PASSED [ 25%]
Config::test_02 FAILED [ 50%]
Config::testClass::test_03 PASSED [ 75%]
Config::testClass::test_04 FAILED [100%]
=================================================================== short test summary info ====================================================================
FAILED Config::test_02 - AssertionError: 测试失败
FAILED Config::testClass::test_04 - AssertionError: 测试失败
================================================================= 2 failed, 2 passed in 0.03s ==================================================================
测试用例使用多个 Fixture
# \test\test_mod1.py
import pytest
@pytest.fixture
def fixture_01():
return [1, 2, 3]
@pytest.fixture
def fixture_02():
return [1, 2, 3]
def test_01(fixture_01, fixture_02):
assert fixture_01 == fixture_02, "测试失败"
运行结果:
===================================================================== test session starts ======================================================================
collected 1 item
Config::test_01 PASSED [100%]
====================================================================== 1 passed in 0.01s =======================================================================
Fixture 函数可以被多个测试用例复用,但是要注意执行顺序是从左到右:
# \test\test_mod1.py
import pytest
data = {"a": 2, "b": 2}
# data["a"]
@pytest.fixture
def fixture_a_01():
data["a"] *= 10
@pytest.fixture
def fixture_a_02():
data["a"] += 10
def test_a(fixture_a_01, fixture_a_02):
assert data["a"] == 30, "测试失败"
# data["b"]
@pytest.fixture
def fixture_b_01():
data["b"] *= 10
@pytest.fixture
def fixture_b_02():
data["b"] += 10
def test_b(fixture_b_02, fixture_b_01):
assert data["b"] == 120, "测试失败"
运行结果:
===================================================================== test session starts ======================================================================
collected 2 items
Config::test_a PASSED [ 50%]
Config::test_b PASSED [100%]
====================================================================== 2 passed in 0.01s =======================================================================
执行顺序不一样,往往会导致结果不同。
Fixture 使用其它 Fixture
# \test\test_mod1.py
import pytest
@pytest.fixture
def fixture_01():
return [1, 2, 3]
@pytest.fixture
def fixture_02(fixture_01):
return fixture_01
def test_01(fixture_02):
assert fixture_02 == [1, 2, 3], "测试失败"
运行结果:
===================================================================== test session starts ======================================================================
collected 1 item
Config::test_01 PASSED [100%]
====================================================================== 1 passed in 0.01s =======================================================================
Fixture 函数作为工厂
# \test\test_mod1.py
import pytest
@pytest.fixture
def make_customer_record():
def make_customer_record(name):
return {"name": name, "orders": []}
return make_customer_record
def test_customer_records(make_customer_record):
customer_1 = make_customer_record("Lisa")
assert customer_1["name"] == "Lisa"
customer_2 = make_customer_record("Mike")
assert customer_2["name"] == "Mike"
customer_3 = make_customer_record("Meredith")
assert customer_3["name"] == "Meredith"
运行结果:
===================================================================== test session starts ======================================================================
collected 1 item
Config::test_customer_records PASSED [100%]
====================================================================== 1 passed in 0.01s =======================================================================
Fixture 的作用域
Fixtures 可以设置为 5 个不同的作用域,分别是:
- session:整个测试会话(即所有模块)只会初始化一次 Fixtures 。
- package:包(即目录)中只会初始化一次 Fixtures 。
- module:模块中只会初始化一次 Fixtures 。
- class:测试组中只会初始化一次 Fixtures 。
- function:每个测试用例都会初始化一次 Fixtures 。
- 设置 Fixtures 作用域为 function
# \test\test_mod1.py
import pytest
@pytest.fixture(scope="function")
def my_list():
return [1, 2, 3]
def test_one(my_list):
my_list.append(4)
assert my_list == [1, 2, 3, 4]
def test_two(my_list):
assert my_list == [1, 2, 3]
def test_three(my_list):
assert my_list == [1, 2, 3]
运行结果:
===================================================================== test session starts ======================================================================
configfile: pytest.ini
plugins: dependency-0.6.0, order-1.3.0
collected 3 items
Config::test_one PASSED [ 33%]
Config::test_two PASSED [ 66%]
Config::test_three PASSED [100%]
====================================================================== 3 passed in 0.01s =======================================================================
每一个测试用例请求 Fixtures:my_list ,都会初始化 my_list 的值,每个测试用例接收的值都是 [1, 2, 3] 。
- 设置 Fixture 作用域为 module
# \test\test_mod1.py
import pytest
@pytest.fixture(scope="module")
def my_list():
return [1, 2, 3]
def test_one(my_list):
my_list.append(4)
assert my_list == [1, 2, 3, 4]
def test_two(my_list):
assert my_list == [1, 2, 3]
def test_three(my_list):
assert my_list == [1, 2, 3]
运行结果:
===================================================================== test session starts ======================================================================
configfile: pytest.ini
plugins: dependency-0.6.0, order-1.3.0
collected 3 items
Config::test_one PASSED [ 33%]
Config::test_two FAILED [ 66%]
Config::test_three FAILED [100%]
=================================================================== short test summary info ====================================================================
FAILED Config::test_two - AssertionError: assert [1, 2, 3, 4] == [1, 2, 3]
FAILED Config::test_three - AssertionError: assert [1, 2, 3, 4] == [1, 2, 3]
================================================================= 2 failed, 1 passed in 0.04s ==================================================================
由于 Fixtures:my_list 在整个模块中只会初始化一次,执行 test_one 后,my_list 的值:[1, 2, 3, 4] 被缓存,后续执行 test_two、test_three 时,my_list 的值都是缓存值:[1, 2, 3, 4] 。
其它作用域的逻辑与 module 一致,只是作用域不同,这里不再赘述。
在 Fixture 使用 yield
在 Fixture 里,可以使用 yield 代替 return,yield 相比 return 多了执行 yield 后代码的功能。
逻辑是这样的:运行测试用例时,首先执行 Fixture 函数;Fixture 函数内部执行到 yield 时,会将数据返回给测试用例;最后测试用例执行结束后,在运行 yield 后面的代码。
我们先设想一个测试流程:
- 新建用户
- 查询数据库,是否新建成功
- 清理新建的用户
利用 yield,我们可以这样写:
# \test\test_mod1.py
import pytest
# 使用字典模拟数据库
user = {}
@pytest.fixture()
def add_user():
user['user1'] = 'JZY'
@pytest.fixture()
def select_user():
yield user['user1']
user.pop('user1')
def test_one(add_user, select_user):
assert select_user == 'JZY', "测试失败"
def test_two():
assert user == {'user1': 'JZY'}, "测试失败"
运行结果:
===================================================================== test session starts ======================================================================
collected 2 items
Config::test_one PASSED [ 50%]
Config::test_two FAILED [100%]
=================================================================== short test summary info ====================================================================
FAILED Config::test_two - AssertionError: 测试失败
================================================================= 1 failed, 1 passed in 0.03s ==================================================================
yield 语句不仅返回值,还在测试完成后执行后续的代码。
多个Fixture 的 yield 以先进后出(从右到左)的顺序执行。比如:
import pytest
def test_bar(fix_w_yield1, fix_w_yield2):
print("test_bar")
@pytest.fixture
def fix_w_yield1():
yield
print("after_yield_1")
@pytest.fixture
def fix_w_yield2():
yield
print("after_yield_2")
运行结果:
============================= test session starts =============================
collecting ... collected 1 item
test/test_a.py::test_bar PASSED [100%]
after_yield_2
after_yield_1
============================== 1 passed in 0.01s ==============================
先执行 fix_w_yield2 的 yield,后执行 fix_w_yield1 的 yield。
传递参数给 Fixture
Mark 标记传参
# \test\test_mod1.py
import pytest
@pytest.fixture
def fixt(request):
marker = request.node.get_closest_marker("user")
if marker is None:
# 未获取到标记,则返回None
data = None
else:
# 获取到标记,则返回标记数据
data = marker.args[0]
return data
# 已在 pytest.ini 中配置了 user 标记
@pytest.mark.user(42)
def test_fixt(fixt):
assert fixt == 42
python .\app.py ,运行结果:
===================================================================== test session starts ======================================================================
collected 1 item
Config::test_fixt PASSED [100%]
====================================================================== 1 passed in 0.01s =======================================================================
定义 Fixture 时,参数化数据
import pytest
# 可以通过 pytest.param("a good person", marks=pytest.mark.user) 对数据进行 mark 标记
@pytest.fixture(params=["JZY", "one man", pytest.param("a good person", marks=pytest.mark.user)])
def fixt(request):
"""
通过 request.param 获取参数值
"""
return request.param
def test_fixt(fixt):
assert (fixt == "JZY") or (fixt == "one man") or (fixt == "a good person")
运行结果:
===================================================================== test session starts ======================================================================
collected 3 items
Config::test_fixt[JZY] PASSED [ 33%]
Config::test_fixt[one man] PASSED [ 66%]
Config::test_fixt[a good person] PASSED [100%]
====================================================================== 3 passed in 0.01s =======================================================================
应用 conftest.py 文件
conftest.py 是 pytest 框架中一个非常重要的特殊文件,它用于定义共享的 fixture、钩子函数(hooks)和配置,这些定义对同一目录及其所有子目录下的测试文件都是可见的。
可以把它理解为一个配置和共享中心,用于集中管理测试所需的公共设置、数据和工具。
pytest 会自动发现 conftest.py 中的 fixture 函数,使用时无需显式导入。
假设有一个测试目录如下:
├── tests/ # 测试用例目录
│ ├── conftest.py # 1
│ ├── __init__.py # 包初始化文件
│ └── login/ # login 模块测试目录
│ ├── conftest.py # 2
│ └── test_login.py # 测试 login 模块的测试用例
注释为 1 的 conftest.py 中 fixture 函数,可以被 tests/ 目录及其子目录下的所有测试用例使用。假设 conftest.py 中有名为 clear 的 fixture 函数,tests/ 目录及其子目录下的所有测试用例,都无需显式导入而直接使用。
注释为 2 的 conftest.py 中 fixture 函数,可以被 login/ 目录及其子目录下的所有测试用例使用。假设 conftest.py 中有名为 clear 的 fixture 函数,login/ 目录及其子目录下的所有测试用例,都无需显式导入而直接使用。非 login/ 目录及其子目录下的所有测试用例,不能不显式导入而直接使用。
自动使用 Fixture
autouse 设置为 true,Pytest 会自动使用 Fixture,常用于初始化操作。比如执行测试前,检查某个条件是否达到要求(如环境变量、配置文件、服务可达性等),如果不满足,直接退出。比如:
# conftest.py
import pytest
import os
@pytest.fixture(autouse=True, scope="session")
def check_test_condition():
# 示例:检查环境变量是否设置
env = os.getenv("TEST_ENV")
if not env:
pytest.exit("❌ 环境变量 TEST_ENV 未设置,终止所有测试!", returncode=1)
if env not in ["dev", "staging"]:
pytest.exit(f"❌ 不支持的环境: {env},仅支持 dev 或 staging", returncode=1)
print(f"✅ 环境检查通过:TEST_ENV={env}")
还有一些 Fixtures 的用法没有在文章中说明,感兴趣的读者可以访问 How to use fixtures 了解,里面有很详细的介绍。
THEEND

© 转载需要保留原始链接,未经明确许可,禁止商业使用。CC BY-NC-ND 4.0